Initial working sequence diagram

This commit is contained in:
Bartek Kryza
2021-02-19 23:35:00 +01:00
parent e6188a355a
commit 6e907deecd
10 changed files with 127 additions and 17689 deletions

View File

@@ -4,15 +4,13 @@ diagrams:
main_sequence_diagram: main_sequence_diagram:
type: sequence type: sequence
glob: glob:
- src/main.cc - t00001/t00001.cc
using_namespace: clanguml using_namespace: clanguml::t00001
start_from: start_from:
file: src/puml/class_diagram_generator.h file: t00001/t00001.cc
line: 108 line: 20
classes: classes:
- config - A
- diagram - B
- class_diagram
puml: puml:
- 'note top of diagram: Aggregate template' - 'note top of diagram: Aggregate template'

View File

@@ -39,18 +39,22 @@ include_directories(${UML_HEADERS_DIR})
include_directories(${THIRDPARTY_HEADERS_DIR}) include_directories(${THIRDPARTY_HEADERS_DIR})
include_directories(${PROJECT_SOURCE_DIR}/src/) include_directories(${PROJECT_SOURCE_DIR}/src/)
add_subdirectory(tests)
file(GLOB_RECURSE SOURCES src/*.cc include/*.h) file(GLOB_RECURSE SOURCES src/*.cc include/*.h)
set(MAIN_SOURCE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cc) set(MAIN_SOURCE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cc)
list(REMOVE_ITEM SOURCES ${MAIN_SOURCE_FILE}) list(REMOVE_ITEM SOURCES ${MAIN_SOURCE_FILE})
add_executable(clang-uml ${SOURCES} ${MAIN_SOURCE_FILE}) add_library(clang-umllib OBJECT ${SOURCES})
add_executable(clang-uml ${MAIN_SOURCE_FILE})
install(TARGETS clang-uml DESTINATION ${CLANG_UML_INSTALL_BIN_DIR}) install(TARGETS clang-uml DESTINATION ${CLANG_UML_INSTALL_BIN_DIR})
target_link_libraries(clang-uml ${LIBCLANG_LIBRARIES} ${YAML_CPP_LIBRARIES} spdlog::spdlog) target_link_libraries(clang-uml ${LIBCLANG_LIBRARIES} ${YAML_CPP_LIBRARIES} spdlog::spdlog clang-umllib)
install( install(
FILES FILES
# add include after DESTINATION, then it works # add include after DESTINATION, then it works
DESTINATION include ${CMAKE_INSTALL_INCLUDEDIR} DESTINATION include ${CMAKE_INSTALL_INCLUDEDIR}
) )
# Enable testing via CTest
enable_testing()
add_subdirectory(tests)

View File

@@ -5,9 +5,9 @@
#include <map> #include <map>
#include <memory> #include <memory>
#include <optional>
#include <string> #include <string>
#include <vector> #include <vector>
#include <optional>
namespace clanguml { namespace clanguml {
namespace config { namespace config {
@@ -18,7 +18,7 @@ struct diagram {
std::string name; std::string name;
std::vector<std::string> glob; std::vector<std::string> glob;
std::vector<std::string> puml; std::vector<std::string> puml;
std::string using_namespace; std::vector<std::string> using_namespace;
}; };
enum class class_scopes { public_, protected_, private_ }; enum class class_scopes { public_, protected_, private_ };
@@ -34,13 +34,15 @@ struct class_diagram : public diagram {
{ {
spdlog::debug("CHECKING IF {} IS WHITE LISTED", clazz); spdlog::debug("CHECKING IF {} IS WHITE LISTED", clazz);
for (const auto &c : classes) { for (const auto &c : classes) {
for (const auto &ns : using_namespace) {
std::string prefix{}; std::string prefix{};
if (!using_namespace.empty()) { if (!ns.empty()) {
prefix = using_namespace + "::"; prefix = ns + "::";
} }
if (prefix + c == clazz) if (prefix + c == clazz)
return true; return true;
} }
}
return false; return false;
} }
@@ -81,7 +83,8 @@ using clanguml::config::source_location;
template <> struct convert<class_diagram> { template <> struct convert<class_diagram> {
static bool decode(const Node &node, class_diagram &rhs) static bool decode(const Node &node, class_diagram &rhs)
{ {
rhs.using_namespace = node["using_namespace"].as<std::string>(); rhs.using_namespace =
node["using_namespace"].as<std::vector<std::string>>();
rhs.glob = node["glob"].as<std::vector<std::string>>(); rhs.glob = node["glob"].as<std::vector<std::string>>();
rhs.puml = node["puml"].as<std::vector<std::string>>(); rhs.puml = node["puml"].as<std::vector<std::string>>();
rhs.classes = node["classes"].as<std::vector<std::string>>(); rhs.classes = node["classes"].as<std::vector<std::string>>();
@@ -98,14 +101,14 @@ template <> struct convert<source_location> {
} }
}; };
// //
// sequence_diagram Yaml decoder // sequence_diagram Yaml decoder
// //
template <> struct convert<sequence_diagram> { template <> struct convert<sequence_diagram> {
static bool decode(const Node &node, sequence_diagram &rhs) static bool decode(const Node &node, sequence_diagram &rhs)
{ {
rhs.using_namespace = node["using_namespace"].as<std::string>(); rhs.using_namespace =
node["using_namespace"].as<std::vector<std::string>>();
rhs.glob = node["glob"].as<std::vector<std::string>>(); rhs.glob = node["glob"].as<std::vector<std::string>>();
rhs.puml = node["puml"].as<std::vector<std::string>>(); rhs.puml = node["puml"].as<std::vector<std::string>>();

View File

@@ -46,6 +46,12 @@ public:
return to_string(clang_getCursorSpelling(m_cursor)); return to_string(clang_getCursorSpelling(m_cursor));
} }
bool is_void() const
{
// Why do I have to do this like this?
return spelling() == "void";
}
std::string fully_qualified() const std::string fully_qualified() const
{ {
std::list<std::string> res; std::list<std::string> res;
@@ -97,6 +103,8 @@ public:
bool is_statement() const { return clang_isStatement(kind()); } bool is_statement() const { return clang_isStatement(kind()); }
bool is_namespace() const { return kind() == CXCursor_Namespace; }
bool is_attribute() const { return clang_isAttribute(kind()); } bool is_attribute() const { return clang_isAttribute(kind()); }
bool has_attrs() const { return clang_Cursor_hasAttrs(m_cursor); } bool has_attrs() const { return clang_Cursor_hasAttrs(m_cursor); }

View File

@@ -1,9 +1,9 @@
#include "config/config.h" #include "config/config.h"
#include "cx/compilation_database.h"
#include "puml/class_diagram_generator.h" #include "puml/class_diagram_generator.h"
#include "puml/sequence_diagram_generator.h" #include "puml/sequence_diagram_generator.h"
#include "uml/class_diagram_model.h" #include "uml/class_diagram_model.h"
#include "uml/class_diagram_visitor.h" #include "uml/class_diagram_visitor.h"
#include "cx/compilation_database.h"
#include "uml/sequence_diagram_visitor.h" #include "uml/sequence_diagram_visitor.h"
#include <cli11/CLI11.hpp> #include <cli11/CLI11.hpp>
@@ -47,7 +47,7 @@ int main(int argc, const char *argv[])
spdlog::info("Loading clang-uml config from {}", config_path); spdlog::info("Loading clang-uml config from {}", config_path);
auto config = config::load(config_path); auto config = clanguml::config::load(config_path);
spdlog::info("Loading compilation database from {} directory", spdlog::info("Loading compilation database from {} directory",
config.compilation_database_dir); config.compilation_database_dir);
@@ -56,8 +56,8 @@ int main(int argc, const char *argv[])
compilation_database::from_directory(config.compilation_database_dir); compilation_database::from_directory(config.compilation_database_dir);
for (const auto &[name, diagram] : config.diagrams) { for (const auto &[name, diagram] : config.diagrams) {
using config::class_diagram; using clanguml::config::class_diagram;
using config::sequence_diagram; using clanguml::config::sequence_diagram;
if (std::dynamic_pointer_cast<class_diagram>(diagram)) { if (std::dynamic_pointer_cast<class_diagram>(diagram)) {
generators::class_diagram::generate( generators::class_diagram::generate(
@@ -67,6 +67,19 @@ int main(int argc, const char *argv[])
generators::sequence_diagram::generate( generators::sequence_diagram::generate(
db, name, dynamic_cast<sequence_diagram &>(*diagram)); db, name, dynamic_cast<sequence_diagram &>(*diagram));
} }
/*
std::filesystem::path path{"puml/" + name + ".puml"};
std::ofstream ofs;
ofs.open(path, std::ofstream::out | std::ofstream::trunc);
// d.sort();
auto generator = puml::generator{diagram, d};
ofs << generator;
ofs.close();
*/
} }
return 0; return 0;
} }

View File

@@ -20,16 +20,28 @@ struct message {
std::string from; std::string from;
std::string from_usr; std::string from_usr;
std::string to; std::string to;
std::string to_usr;
std::string message; std::string message;
std::string return_type;
unsigned int line; unsigned int line;
}; };
struct activity {
std::string usr;
std::string from;
std::vector<message> messages;
};
struct diagram { struct diagram {
bool started{false};
std::string name; std::string name;
std::vector<message> sequence;
//std::map<std::string, activity> sequences;
std::map<std::string, activity> sequences;
void sort() void sort()
{ {
/*
std::sort(sequence.begin(), sequence.end(), std::sort(sequence.begin(), sequence.end(),
[](const auto &a, const auto &b) -> bool { [](const auto &a, const auto &b) -> bool {
if (a.from_usr == b.from_usr) if (a.from_usr == b.from_usr)
@@ -37,6 +49,7 @@ struct diagram {
return a.from_usr > b.from_usr; return a.from_usr > b.from_usr;
}); });
*/
} }
}; };
} }

View File

@@ -15,6 +15,7 @@ namespace clanguml {
namespace visitor { namespace visitor {
namespace sequence_diagram { namespace sequence_diagram {
using clanguml::model::sequence_diagram::activity;
using clanguml::model::sequence_diagram::diagram; using clanguml::model::sequence_diagram::diagram;
using clanguml::model::sequence_diagram::message; using clanguml::model::sequence_diagram::message;
using clanguml::model::sequence_diagram::message_t; using clanguml::model::sequence_diagram::message_t;
@@ -67,31 +68,43 @@ static enum CXChildVisitResult translation_unit_visitor(
unsigned int offset{}; unsigned int offset{};
clang_getFileLocation( clang_getFileLocation(
cursor.location(), &f, &line, &column, &offset); cursor.location(), &f, &line, &column, &offset);
std::string file{clang_getCString(clang_getFileName(f))};
auto &d = ctx->d;
if (referenced.kind() == CXCursor_CXXMethod) { if (referenced.kind() == CXCursor_CXXMethod) {
if (sp_name.find("clanguml::") == 0) { if (true/*sp_name.find("clanguml::") == 0 ||
clang_Location_isFromMainFile(cursor.location())*/) {
// Get calling object // Get calling object
std::string caller = std::string caller{};
ctx->current_method.semantic_parent().fully_qualified(); if (ctx->current_method.semantic_parent()
if (caller.empty() && .is_translation_unit() ||
clang_Location_isFromMainFile(cursor.location()) == 0) ctx->current_method.semantic_parent().is_namespace()) {
caller = "<MAIN>"; caller =
ctx->current_method.semantic_parent().fully_qualified() +
"::" + ctx->current_method.spelling() + "()";
}
else {
caller = ctx->current_method.semantic_parent().fully_qualified();
}
std::string caller_usr = ctx->current_method.usr(); auto caller_usr = ctx->current_method.usr();
// Get called object // Get called object
std::string callee = auto callee =
cursor.referenced().semantic_parent().fully_qualified(); referenced.semantic_parent().fully_qualified();
auto callee_usr = referenced.semantic_parent().usr();
// Get called method // Get called method
std::string called_message = cursor.spelling(); auto called_message = cursor.spelling();
// Found method call: CXCursorKind () const // Found method call: CXCursorKind () const
spdlog::debug("Found method call at line {}:{} " spdlog::debug(
"\n\tCURRENT_METHOD: {}\n\tFROM: {}\n\tTO: " "Adding method call at line {}:{} to diagram {}"
"{}\n\tMESSAGE: {}", "\n\tCURRENT_METHOD: {}\n\tFROM: '{}'\n\tTO: "
clang_getCString(clang_getFileName(f)), line, "{}\n\tMESSAGE: {}\n\tFROM_USR: {}\n\tTO_USR: "
ctx->current_method.spelling(), caller, callee, "{}\n\tRETURN_TYPE: {}",
called_message); file, line, d.name, ctx->current_method.spelling(),
caller, callee, called_message, caller_usr, callee_usr,
referenced.type().result_type().spelling());
message m; message m;
m.type = message_t::kCall; m.type = message_t::kCall;
@@ -99,16 +112,25 @@ static enum CXChildVisitResult translation_unit_visitor(
m.from_usr = caller_usr; m.from_usr = caller_usr;
m.line = line; m.line = line;
m.to = callee; m.to = callee;
m.to_usr = referenced.usr();
m.message = called_message; m.message = called_message;
m.return_type = referenced.type().result_type().spelling();
ctx->d.sequence.emplace_back(std::move(m)); if (d.sequences.find(caller_usr) == d.sequences.end()) {
activity a;
a.usr = caller_usr;
a.from = caller;
d.sequences.insert({caller_usr, std::move(a)});
}
d.sequences[caller_usr].messages.emplace_back(std::move(m));
} }
} }
else if (referenced.kind() == CXCursor_FunctionDecl) { else if (referenced.kind() == CXCursor_FunctionDecl) {
// TODO // TODO
} }
ret = CXChildVisit_Continue; ret = CXChildVisit_Recurse;
break; break;
} }
case CXCursor_Namespace: { case CXCursor_Namespace: {

View File

@@ -1,21 +1,27 @@
cmake_minimum_required(VERSION 3.10) cmake_minimum_required(VERSION 3.10)
set(CMAKE_CXX_STANDARD 20)
# Explicitly list the test source code and headers. The Catch header-only unit set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# test framework is stored in with the test source.
set(CLANG_UML_TEST_EXAMPLE_SRC set(CMAKE_CXX_STANDARD 17)
test_example.cpp
set(CLANG_UML_TEST_CASES_SRC
test_cases.cc
t00001/t00001.cc
) )
set(CLANG_UML_TEST_EXAMPLE_HEADER set(CLANG_UML_TEST_CASES_HEADER
catch.hpp catch.h
) )
# Make an executable target that depends on the test source code we specified add_executable(test_cases
# above. ${CLANG_UML_TEST_CASES_SRC}
add_executable(test-example ${CLANG_UML_TEST_EXAMPLE_SRC} ${CLANG_UML_TEST_EXAMPL_HEADER}) ${CLANG_UML_TEST_CASES_HEADER})
# Enable testing via CTest target_link_libraries(test_cases
enable_testing() PRIVATE
${LIBCLANG_LIBRARIES}
${YAML_CPP_LIBRARIES}
spdlog::spdlog clang-umllib)
# Add our test as runnable via CTest configure_file(t00001/.clanguml t00001/.clanguml COPYONLY)
add_test(NAME test-example COMMAND test-example)
add_test(NAME test_cases COMMAND test_cases)

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +0,0 @@
#define CATCH_CONFIG_MAIN
#include "catch.hpp"
#include <string>
#include <complex>
TEST_CASE("Test add", "[unit-test]"){
// not very good tests, but oh well...
REQUIRE(2+5 == 5);
std::cout << "RUNNING TEST" << std::endl;
}