Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/cpp #152

Merged
merged 27 commits into from
Jun 1, 2023
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.
Jump to
Jump to file
Failed to load files.
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
mpkorstanje marked this conversation as resolved.
Show resolved Hide resolved
${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 -%>