From 0c08507d34e30728d15eebd3245b0a7a43244107 Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Sat, 20 Feb 2021 12:30:44 +0100 Subject: [PATCH] Added PlantUML directives prepending and appending --- .github/workflows/build.yml | 4 +- .gitignore | 3 +- src/config/config.h | 52 ++++--- src/puml/class_diagram_generator.h | 197 ++++++++++++++++++++++++++ src/puml/sequence_diagram_generator.h | 165 +++++++++++++++++++++ tests/t00001/.clanguml | 16 ++- 6 files changed, 412 insertions(+), 25 deletions(-) create mode 100644 src/puml/class_diagram_generator.h create mode 100644 src/puml/sequence_diagram_generator.h diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 34420f1d..ff2ce622 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,6 +14,6 @@ jobs: run: | mkdir build cd build - cmake .. -DCMAKE_BUILD_TYPE=Release + cmake -DCMAKE_BUILD_TYPE=Debug .. make -j - ctest + make test diff --git a/.gitignore b/.gitignore index 9436d678..83f6e0f4 100644 --- a/.gitignore +++ b/.gitignore @@ -13,5 +13,4 @@ build/ lib/ bin/ *.swp - -puml +/puml/ diff --git a/src/config/config.h b/src/config/config.h index aea8e37b..0019c728 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -7,18 +7,31 @@ #include #include #include +#include #include namespace clanguml { namespace config { +struct plantuml { + std::vector before; + std::vector after; +}; + struct diagram { virtual ~diagram() = default; std::string name; std::vector glob; - std::vector puml; std::vector using_namespace; + plantuml puml; +}; + +struct source_location { + using usr = std::string; + using marker = std::string; + using file = std::pair; + using variant = std::variant; }; enum class class_scopes { public_, protected_, private_ }; @@ -32,7 +45,6 @@ struct class_diagram : public diagram { bool has_class(std::string clazz) { - spdlog::debug("CHECKING IF {} IS WHITE LISTED", clazz); for (const auto &c : classes) { for (const auto &ns : using_namespace) { std::string prefix{}; @@ -48,15 +60,10 @@ struct class_diagram : public diagram { } }; -struct source_location { - std::string file; - unsigned int line; -}; - struct sequence_diagram : public diagram { virtual ~sequence_diagram() = default; - std::optional start_from; + std::vector start_from; }; struct config { @@ -74,6 +81,7 @@ config load(const std::string &config_file); namespace YAML { using clanguml::config::class_diagram; using clanguml::config::config; +using clanguml::config::plantuml; using clanguml::config::sequence_diagram; using clanguml::config::source_location; @@ -86,17 +94,29 @@ template <> struct convert { rhs.using_namespace = node["using_namespace"].as>(); rhs.glob = node["glob"].as>(); - rhs.puml = node["puml"].as>(); - rhs.classes = node["classes"].as>(); + if (node["puml"]) + rhs.puml = node["plantuml"].as(); return true; } }; -template <> struct convert { - static bool decode(const Node &node, source_location &rhs) +template <> struct convert { + static bool decode(const Node &node, source_location::variant &rhs) { - rhs.file = node["file"].as(); - rhs.line = node["line"].as(); + if(node["usr"]) + rhs = node["usr"].as(); + return true; + } +}; + +template <> struct convert { + static bool decode(const Node &node, plantuml &rhs) + { + if (node["before"]) + rhs.before = node["before"].as(); + + if (node["after"]) + rhs.after = node["after"].as(); return true; } }; @@ -110,10 +130,10 @@ template <> struct convert { rhs.using_namespace = node["using_namespace"].as>(); rhs.glob = node["glob"].as>(); - rhs.puml = node["puml"].as>(); + rhs.puml = node["plantuml"].as(); if (node["start_from"]) - rhs.start_from = node["start_from"].as(); + rhs.start_from = node["start_from"].as(); return true; } }; diff --git a/src/puml/class_diagram_generator.h b/src/puml/class_diagram_generator.h new file mode 100644 index 00000000..1d1fb4d3 --- /dev/null +++ b/src/puml/class_diagram_generator.h @@ -0,0 +1,197 @@ +#pragma once + +#include "config/config.h" +#include "cx/compilation_database.h" +#include "uml/class_diagram_model.h" +#include "uml/class_diagram_visitor.h" +#include "util/util.h" + +#include + +#include +#include +#include +#include + +namespace clanguml { +namespace generators { +namespace class_diagram { +namespace puml { + +using diagram_config = clanguml::config::class_diagram::diagram; +using diagram_model = clanguml::model::class_diagram::diagram; +using clanguml::model::class_diagram::class_; +using clanguml::model::class_diagram::class_relationship; +using clanguml::model::class_diagram::element; +using clanguml::model::class_diagram::enum_; +using clanguml::model::class_diagram::relationship_t; +using clanguml::model::class_diagram::scope_t; +using clanguml::visitor::class_diagram::tu_context; +using namespace clanguml::util; + +std::string relative_to(std::string n, std::string c) +{ + if (c.rfind(n) == std::string::npos) + return c; + + return c.substr(n.size() + 2); +} + +class generator { +public: + generator(clanguml::config::class_diagram &config, diagram_model &model) + : m_config(config) + , m_model(model) + { + } + + std::string to_string(scope_t scope) const + { + switch (scope) { + case scope_t::kPublic: + return "+"; + case scope_t::kProtected: + return "#"; + case scope_t::kPrivate: + return "-"; + default: + return ""; + } + } + + std::string to_string(relationship_t r) const + { + switch (r) { + case relationship_t::kComposition: + return "*--"; + case relationship_t::kAggregation: + return "o--"; + case relationship_t::kContainment: + return "+--"; + case relationship_t::kAssociation: + return "--"; + default: + return ""; + } + } + + void generate(const class_ &c, std::ostream &ostr) const + { + ostr << "Class " << c.name << " {" << std::endl; + + for (const auto &m : c.methods) { + ostr << to_string(m.scope) << m.type << " " << m.name + "()" + << std::endl; + } + + for (const auto &m : c.members) { + ostr << to_string(m.scope) << m.type << " " << m.name << std::endl; + } + + ostr << "}" << std::endl; + + for (const auto &b : c.bases) { + ostr << b.name << " <|-- " << c.name << std::endl; + } + + for (const auto &r : c.relationships) { + // only add UML relationships to classes in the diagram + if (m_config.has_class(r.destination)) + ostr << c.name << " " << to_string(r.type) << " " + << namespace_relative(m_config.using_namespace, r.destination) + << " : " << r.label << std::endl; + } + } + + void generate(const enum_ &e, std::ostream &ostr) const + { + ostr << "Enum " << e.name << " {" << std::endl; + + for (const auto &enum_constant : e.constants) { + ostr << enum_constant << std::endl; + } + + ostr << "}" << std::endl; + } + + void generate(std::ostream &ostr) const + { + ostr << "@startuml" << std::endl; + + for (const auto &c : m_model.classes) { + generate(c, ostr); + ostr << std::endl; + } + + for (const auto &e : m_model.enums) { + generate(e, ostr); + ostr << std::endl; + } + + ostr << "@enduml" << std::endl; + } + + friend std::ostream &operator<<(std::ostream &os, const generator &g); + +private: + clanguml::config::class_diagram &m_config; + diagram_model &m_model; +}; + +std::ostream &operator<<(std::ostream &os, const generator &g) +{ + g.generate(os); + return os; +} +} + +clanguml::model::class_diagram::diagram generate( + clanguml::cx::compilation_database &db, const std::string &name, + clanguml::config::class_diagram &diagram) +{ + spdlog::info("Generating diagram {}.puml", name); + clanguml::model::class_diagram::diagram d; + d.name = name; + + // Get all translation units matching the glob from diagram + // configuration + std::vector translation_units{}; + for (const auto &g : diagram.glob) { + spdlog::debug("Processing glob: {}", g); + const auto matches = glob::glob(g); + std::copy(matches.begin(), matches.end(), + std::back_inserter(translation_units)); + } + + // Process all matching translation units + for (const auto &tu_path : translation_units) { + spdlog::debug("Processing translation unit: {}", + std::filesystem::canonical(tu_path).c_str()); + + auto tu = db.parse_translation_unit(tu_path); + + auto cursor = clang_getTranslationUnitCursor(tu); + + if (clang_Cursor_isNull(cursor)) { + spdlog::debug("CURSOR IS NULL"); + } + + spdlog::debug("Cursor kind: {}", + clang_getCString(clang_getCursorKindSpelling(cursor.kind))); + spdlog::debug("Cursor name: {}", + clang_getCString(clang_getCursorDisplayName(cursor))); + + clanguml::visitor::class_diagram::tu_context ctx(d); + auto res = clang_visitChildren(cursor, + clanguml::visitor::class_diagram::translation_unit_visitor, &ctx); + spdlog::debug("Processing result: {}", res); + + clang_suspendTranslationUnit(tu); + } + + return d; +} +} +} +} + diff --git a/src/puml/sequence_diagram_generator.h b/src/puml/sequence_diagram_generator.h new file mode 100644 index 00000000..ab1b8708 --- /dev/null +++ b/src/puml/sequence_diagram_generator.h @@ -0,0 +1,165 @@ +#pragma once + +#include "config/config.h" +#include "uml/sequence_diagram_model.h" +#include "uml/sequence_diagram_visitor.h" +#include "util/util.h" + +#include + +#include +#include +#include +#include + +namespace clanguml { +namespace generators { +namespace sequence_diagram { +namespace puml { + +using diagram_model = clanguml::model::sequence_diagram::diagram; +using diagram_config = clanguml::config::sequence_diagram::diagram; +using clanguml::model::sequence_diagram::activity; +using clanguml::model::sequence_diagram::message; +using clanguml::model::sequence_diagram::message_t; +using clanguml::visitor::sequence_diagram::tu_context; +using namespace clanguml::util; + +class generator { +public: + generator(clanguml::config::sequence_diagram &config, diagram_model &model) + : m_config(config) + , m_model(model) + { + } + + std::string to_string(message_t r) const + { + switch (r) { + case message_t::kCall: + return "->"; + case message_t::kReturn: + return "<--"; + default: + return ""; + } + } + + void generate_call(const message &m, std::ostream &ostr) const + { + const auto from = namespace_relative(m_config.using_namespace, m.from); + const auto to = namespace_relative(m_config.using_namespace, m.to); + + ostr << '"' << from << "\" " + << "->" + << " \"" << to << "\" : " << m.message << "()" << std::endl; + } + + void generate_return(const message &m, std::ostream &ostr) const + { + if ((m.from != m.to) && (m.return_type != "void")) { + const auto from = + namespace_relative(m_config.using_namespace, m.from); + const auto to = namespace_relative(m_config.using_namespace, m.to); + + ostr << '"' << to << "\" " + << "-->" + << " \"" << from << "\"" << std::endl; + } + } + + void generate_activity(const activity &a, std::ostream &ostr) const + { + for (const auto &m : a.messages) { + const auto to = namespace_relative(m_config.using_namespace, m.to); + generate_call(m, ostr); + ostr << "activate " << '"' << to << '"' << std::endl; + if (m_model.sequences.find(m.to_usr) != m_model.sequences.end()) + generate_activity(m_model.sequences[m.to_usr], ostr); + generate_return(m, ostr); + ostr << "deactivate " << '"' << to << '"' << std::endl; + } + } + + void generate(std::ostream &ostr) const + { + auto start_from = "c:@N@clanguml@N@t00001@F@tmain#"; + ostr << "@startuml" << std::endl; + + for (const auto &b : m_config.puml.before) + ostr << b << std::endl; + + generate_activity(m_model.sequences[start_from], ostr); + + for (const auto &a : m_config.puml.after) + ostr << a << std::endl; + + ostr << "@enduml" << std::endl; + } + + friend std::ostream &operator<<(std::ostream &os, const generator &g); + +private: + clanguml::config::sequence_diagram &m_config; + clanguml::model::sequence_diagram::diagram &m_model; +}; + +std::ostream &operator<<(std::ostream &os, const generator &g) +{ + g.generate(os); + return os; +} +} + +clanguml::model::sequence_diagram::diagram generate( + clanguml::cx::compilation_database &db, const std::string &name, + clanguml::config::sequence_diagram &diagram) +{ + spdlog::info("Generating diagram {}.puml", name); + clanguml::model::sequence_diagram::diagram d; + d.name = name; + + // Get all translation units matching the glob from diagram + // configuration + std::vector translation_units{}; + for (const auto &g : diagram.glob) { + spdlog::debug("Processing glob: {}", g); + const auto matches = glob::glob(g); + std::copy(matches.begin(), matches.end(), + std::back_inserter(translation_units)); + } + + // Process all matching translation units + for (const auto &tu_path : translation_units) { + spdlog::debug("Processing translation unit: {}", + std::filesystem::canonical(tu_path).c_str()); + + auto tu = db.parse_translation_unit(tu_path); + + auto cursor = clang_getTranslationUnitCursor(tu); + + if (clang_Cursor_isNull(cursor)) { + spdlog::debug("CURSOR IS NULL"); + } + + spdlog::debug("Cursor kind: {}", + clang_getCString(clang_getCursorKindSpelling(cursor.kind))); + spdlog::debug("Cursor name: {}", + clang_getCString(clang_getCursorDisplayName(cursor))); + + clanguml::visitor::sequence_diagram::tu_context ctx(d); + auto res = clang_visitChildren(cursor, + clanguml::visitor::sequence_diagram::translation_unit_visitor, + &ctx); + + spdlog::debug("Processing result: {}", res); + + clang_suspendTranslationUnit(tu); + } + + return d; +} +} +} +} + diff --git a/tests/t00001/.clanguml b/tests/t00001/.clanguml index f02d7582..7a6cfa1d 100644 --- a/tests/t00001/.clanguml +++ b/tests/t00001/.clanguml @@ -5,12 +5,18 @@ diagrams: type: sequence glob: - ../../tests/t00001/t00001.cc + include: + namespace: + - clanguml::t00001 + exclude: + namespace: + - std using_namespace: - clanguml::t00001 start_from: - usr: "c:@N@clanguml@N@t00001@F@tmain#" - classes: - - A - - B - puml: - - 'note top of diagram: Aggregate template' + plantuml: + before: + - "' t00001 test sequence diagram" + after: + - 'note over "tmain()": Main test function'