Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cpp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
build/
include/cucumber/messages/
src/lib/cucumber-messages/cucumber/messages/
28 changes: 28 additions & 0 deletions cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
cmake_minimum_required(VERSION 3.12 FATAL_ERROR)

project(cucumber-messages VERSION 1.0.0 LANGUAGES C CXX)

include(GNUInstallDirs)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)

add_subdirectory(src)

install(
TARGETS
cucumber-messages_library
EXPORT cucumber-messages-config
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)

install(
EXPORT cucumber-messages-config
FILE cucumber-messages-config.cmake
NAMESPACE cucumber::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cucumber-messages
)
38 changes: 38 additions & 0 deletions cpp/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
schemas = $(shell find ../jsonschema -name "*.json")

.DEFAULT_GOAL = help

INC_DIR = include/cucumber/messages
LIB_DIR = src/lib/cucumber-messages/cucumber/messages
GEN_DIRS = $(INC_DIR) $(LIB_DIR)

help: ## Show this help
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make <target>\n\nWhere <target> is one of:\n"} /^[$$()% a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)

generate: require .generate-messages ## Generate C++ code based on the schemas found in ../jsonschema and using the scripts in ../jsonschema/scripts for the generation

require: ## Check requirements for the code generation (ruby, csplit and tail are required)
@ruby --version >/dev/null 2>&1 || (echo "ERROR: ruby is required."; exit 1)
@csplit --version >/dev/null 2>&1 || (echo "ERROR: csplit is required."; exit 1)
@tail --version >/dev/null 2>&1 || (echo "ERROR: tail is required."; exit 1)

clean: ## Remove automatically generated files and related artifacts
rm -rf $(GEN_DIRS)

.generate-messages: $(schemas) ../jsonschema/scripts/codegen.rb ../jsonschema/scripts/templates/cpp.hpp.erb ../jsonschema/scripts/templates/cpp.enum.hpp.erb
ruby ../jsonschema/scripts/codegen.rb Cpp ../jsonschema cpp.hpp.erb > Generated.hpp.tmp
ruby ../jsonschema/scripts/codegen.rb Cpp ../jsonschema cpp.enum.hpp.erb >> Generated.hpp.tmp
csplit --quiet --prefix=Generated --suffix-format=%02d.hpp.tmp --elide-empty-files Generated.hpp.tmp /^[A-Za-z_.]*[.][ch]pp/ {*}
rm Generated.hpp.tmp
rm -rf $(GEN_DIRS)
mkdir -p $(GEN_DIRS)

for file in Generated**; do \
F=$$(head -n 1 $$file | tr -d '\r\n'); \
if [ -n "$$F" ]; then \
tail -n +2 $$file > $(INC_DIR)/$$F; \
fi; \
rm $$file; \
done

mv $(INC_DIR)/*.cpp $(LIB_DIR)/ || true
43 changes: 43 additions & 0 deletions cpp/include/cucumber/message.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#pragma once

#include <type_traits>
#include <vector>

namespace cucumber {

struct message
{
virtual std::string to_string() const = 0;
};

template <
typename Msg,
typename = std::enable_if_t<std::is_base_of_v<message, Msg>>
>
std::ostream&
operator<<(std::ostream& os, const Msg& msg)
{
os << msg.to_string();

return os;
}

template <
typename Msg,
typename = std::enable_if_t<std::is_base_of_v<message, Msg>>
>
std::ostream&
operator<<(std::ostream& os, const std::vector<Msg>& msgs)
{
os << '[';

for (std::size_t i = 0; i < msgs.size(); ++i) {
os << (i > 0 ? ", " : "") << msgs[i];
}

os << ']';

return os;
}

}
1 change: 1 addition & 0 deletions cpp/src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add_subdirectory(lib)
1 change: 1 addition & 0 deletions cpp/src/lib/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add_subdirectory(cucumber-messages)
142 changes: 142 additions & 0 deletions cpp/src/lib/cucumber-messages/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
add_library(cucumber-messages_library)
add_library(cucumber::messages ALIAS cucumber-messages_library)

set(INC_DIR "${CMAKE_SOURCE_DIR}/include")

target_sources(
cucumber-messages_library
PRIVATE
${INC_DIR}/cucumber/messages/attachment_content_encoding.hpp
${INC_DIR}/cucumber/messages/attachment.hpp
${INC_DIR}/cucumber/messages/background.hpp
${INC_DIR}/cucumber/messages/ci.hpp
${INC_DIR}/cucumber/messages/comment.hpp
${INC_DIR}/cucumber/messages/data_table.hpp
${INC_DIR}/cucumber/messages/doc_string.hpp
${INC_DIR}/cucumber/messages/duration.hpp
${INC_DIR}/cucumber/messages/envelope.hpp
${INC_DIR}/cucumber/messages/examples.hpp
${INC_DIR}/cucumber/messages/exception.hpp
${INC_DIR}/cucumber/messages/feature_child.hpp
${INC_DIR}/cucumber/messages/feature.hpp
${INC_DIR}/cucumber/messages/gherkin_document.hpp
${INC_DIR}/cucumber/messages/git.hpp
${INC_DIR}/cucumber/messages/group.hpp
${INC_DIR}/cucumber/messages/hook.hpp
${INC_DIR}/cucumber/messages/java_method.hpp
${INC_DIR}/cucumber/messages/java_stack_trace_element.hpp
${INC_DIR}/cucumber/messages/location.hpp
${INC_DIR}/cucumber/messages/meta.hpp
${INC_DIR}/cucumber/messages/parameter_type.hpp
${INC_DIR}/cucumber/messages/parse_error.hpp
${INC_DIR}/cucumber/messages/pickle_doc_string.hpp
${INC_DIR}/cucumber/messages/pickle.hpp
${INC_DIR}/cucumber/messages/pickle_step_argument.hpp
${INC_DIR}/cucumber/messages/pickle_step.hpp
${INC_DIR}/cucumber/messages/pickle_step_type.hpp
${INC_DIR}/cucumber/messages/pickle_table_cell.hpp
${INC_DIR}/cucumber/messages/pickle_table.hpp
${INC_DIR}/cucumber/messages/pickle_table_row.hpp
${INC_DIR}/cucumber/messages/pickle_tag.hpp
${INC_DIR}/cucumber/messages/product.hpp
${INC_DIR}/cucumber/messages/rule_child.hpp
${INC_DIR}/cucumber/messages/rule.hpp
${INC_DIR}/cucumber/messages/scenario.hpp
${INC_DIR}/cucumber/messages/source.hpp
${INC_DIR}/cucumber/messages/source_media_type.hpp
${INC_DIR}/cucumber/messages/source_reference.hpp
${INC_DIR}/cucumber/messages/step_definition.hpp
${INC_DIR}/cucumber/messages/step_definition_pattern.hpp
${INC_DIR}/cucumber/messages/step_definition_pattern_type.hpp
${INC_DIR}/cucumber/messages/step.hpp
${INC_DIR}/cucumber/messages/step_keyword_type.hpp
${INC_DIR}/cucumber/messages/step_match_argument.hpp
${INC_DIR}/cucumber/messages/step_match_arguments_list.hpp
${INC_DIR}/cucumber/messages/table_cell.hpp
${INC_DIR}/cucumber/messages/table_row.hpp
${INC_DIR}/cucumber/messages/tag.hpp
${INC_DIR}/cucumber/messages/test_case_finished.hpp
${INC_DIR}/cucumber/messages/test_case.hpp
${INC_DIR}/cucumber/messages/test_case_started.hpp
${INC_DIR}/cucumber/messages/test_run_finished.hpp
${INC_DIR}/cucumber/messages/test_run_started.hpp
${INC_DIR}/cucumber/messages/test_step_finished.hpp
${INC_DIR}/cucumber/messages/test_step.hpp
${INC_DIR}/cucumber/messages/test_step_result.hpp
${INC_DIR}/cucumber/messages/test_step_result_status.hpp
${INC_DIR}/cucumber/messages/test_step_started.hpp
${INC_DIR}/cucumber/messages/timestamp.hpp
${INC_DIR}/cucumber/messages/undefined_parameter_type.hpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/utils.hpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/attachment.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/background.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/ci.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/comment.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/data_table.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/doc_string.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/duration.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/envelope.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/examples.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/exception.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/feature_child.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/feature.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/gherkin_document.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/git.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/group.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/hook.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/java_method.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/java_stack_trace_element.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/location.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/meta.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/parameter_type.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/parse_error.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/pickle.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/pickle_doc_string.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/pickle_step_argument.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/pickle_step.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/pickle_table_cell.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/pickle_table.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/pickle_table_row.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/pickle_tag.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/product.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/rule_child.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/rule.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/scenario.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/source.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/source_reference.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/step.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/step_definition.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/step_definition_pattern.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/step_match_argument.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/step_match_arguments_list.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/table_cell.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/table_row.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/tag.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/test_case.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/test_case_finished.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/test_case_started.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/test_run_finished.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/test_run_started.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/test_step.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/test_step_finished.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/test_step_result.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/test_step_started.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/timestamp.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cucumber/messages/undefined_parameter_type.cpp
)

target_include_directories(
cucumber-messages_library
PUBLIC
$<BUILD_INTERFACE:${INC_DIR}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/cucumber>
PRIVATE
${INC_DIR}
${CMAKE_CURRENT_SOURCE_DIR}
)

set_target_properties(
cucumber-messages_library
PROPERTIES
OUTPUT_NAME cucumber_messages
)
26 changes: 26 additions & 0 deletions cpp/src/lib/cucumber-messages/cucumber/utils.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#pragma once

#include <string>
#include <sstream>
#include <vector>

namespace cucumber {

template <typename Msg>
std::string
to_string(const std::vector<Msg>& msgs)
{
std::ostringstream oss;

oss << '[';

for (std::size_t i = 0; i < msgs.size(); ++i) {
oss << (i > 0 ? ", " : "") << msgs[i];
}

oss << ']';

return oss.str();
}

}
27 changes: 27 additions & 0 deletions jsonschema/scripts/codegen.rb
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,33 @@ def array_type_for(type_name)
end
end

class Cpp < Codegen
def initialize(paths)
language_type_by_schema_type = {
'integer' => 'std::size_t',
'string' => 'std::string',
'boolean' => 'bool',
}

super(paths, language_type_by_schema_type)
end

def array_type_for(type_name)
"std::vector<#{type_name}>"
end

def format_description(raw_description, indent_string: "")
return '' if raw_description.nil?

raw_description
.split("\n")
.map { |line| line.strip() }
.filter { |line| line != '*' }
.map { |line| "// #{line}".rstrip() }
.join("\n#{indent_string}")
end
end

class Java < Codegen
def initialize(paths)
language_type_by_schema_type = {
Expand Down
54 changes: 54 additions & 0 deletions jsonschema/scripts/templates/cpp.enum.hpp.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<% @enums.each do |enum| -%>
<% ename = underscore(enum[:name]) %>
<%= ename %>.hpp
#pragma once

#include <string_view>

namespace cucumber::messages {

enum class <%= ename %>
{
<%-
vals = enum[:values].map { |v| enum_constant(v) }
-%>
<%= vals.join(",\n ") %>
};

std::string_view
to_string(<%= ename %> v);

std::ostream&
operator<<(std::ostream& os, <%= ename %> v);

}
<%= ename %>.cpp
#include <unordered_map>

#include <cucumber/messages/<%= ename %>.hpp>

namespace cucumber::messages {

std::string_view
to_string(<%= ename %> v)
{
using map_type = std::unordered_map<<%= ename %>, std::string_view>;

static const map_type m = {
<%- enum[:values].each_with_index do |value, index| -%>
{ <%= enum_constant(value) %>, "<%= value %>" }<%= index < enum[:values].length-1 ? ',' : '' %>
<% end -%>
};

return m.at(v);
}

std::ostream&
operator<<(std::ostream& os, <%= ename %> v)
{
os << to_string(v);

return os;
}

}<%- end -%>
Loading