diff --git a/.gitignore b/.gitignore index 077bb710..6814e7fc 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,9 @@ bin/ /debug/ /release/ /.cache + + +# CLion + +.idea/ +cmake-build-debug/ \ No newline at end of file diff --git a/src/class_diagram/generators/plantuml/class_diagram_generator.cc b/src/class_diagram/generators/plantuml/class_diagram_generator.cc index 72365395..94b59f25 100644 --- a/src/class_diagram/generators/plantuml/class_diagram_generator.cc +++ b/src/class_diagram/generators/plantuml/class_diagram_generator.cc @@ -414,7 +414,7 @@ clanguml::class_diagram::model::diagram generate( cppast::libclang_compilation_database &db, const std::string &name, clanguml::config::class_diagram &diagram) { - spdlog::info("Generating diagram {}.puml", name); + LOG_DBG("Generating diagram {}.puml", name); clanguml::class_diagram::model::diagram d; d.set_name(name); @@ -422,7 +422,7 @@ clanguml::class_diagram::model::diagram generate( // configuration std::vector translation_units{}; for (const auto &g : diagram.glob) { - spdlog::debug("Processing glob: {}", g); + LOG_DBG("Processing glob: {}", g); const auto matches = glob::rglob(g); std::copy(matches.begin(), matches.end(), std::back_inserter(translation_units)); diff --git a/src/config/config.cc b/src/config/config.cc index 8edb024d..75c88259 100644 --- a/src/config/config.cc +++ b/src/config/config.cc @@ -69,7 +69,7 @@ bool diagram::should_include(const std::string &name_) const for (const auto &ex : exclude.namespaces) { if (name.find(ex) == 0) { - spdlog::debug("Skipping from diagram: {}", name); + LOG_DBG("Skipping from diagram: {}", name); return false; } } @@ -132,6 +132,7 @@ using clanguml::common::model::scope_t; using clanguml::config::class_diagram; using clanguml::config::config; using clanguml::config::filter; +using clanguml::config::package_diagram; using clanguml::config::plantuml; using clanguml::config::sequence_diagram; using clanguml::config::source_location; @@ -282,6 +283,19 @@ template <> struct convert { } }; +// +// class_diagram Yaml decoder +// +template <> struct convert { + static bool decode(const Node &node, package_diagram &rhs) + { + if (!decode_diagram(node, rhs)) + return false; + + return true; + } +}; + // // config Yaml decoder // @@ -313,8 +327,12 @@ template <> struct convert { rhs.diagrams[name] = std::make_shared( d.second.as()); } + else if (diagram_type == "package") { + rhs.diagrams[name] = std::make_shared( + d.second.as()); + } else { - spdlog::warn( + LOG_WARN( "Diagrams of type {} are not supported at the moment... ", diagram_type); } diff --git a/src/config/config.h b/src/config/config.h index 6f5e04ad..aae0d1d1 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -101,6 +101,10 @@ struct sequence_diagram : public diagram { std::vector start_from; }; +struct package_diagram : public diagram { + virtual ~package_diagram() = default; +}; + struct config { // the glob list is additive and relative to the current // directory diff --git a/src/cx/compilation_database.cc b/src/cx/compilation_database.cc index d22e24f9..c2842364 100644 --- a/src/cx/compilation_database.cc +++ b/src/cx/compilation_database.cc @@ -16,6 +16,7 @@ * limitations under the License. */ #include "compilation_database.h" +#include "util/util.h" #include #include @@ -73,7 +74,7 @@ CXTranslationUnit compilation_database::parse_translation_unit( clang_CompileCommands_getCommand(compile_commands, 0); auto cc_filename = clang_CompileCommand_getFilename(compile_command); - spdlog::debug( + LOG_DBG( "Processing compile command file: {}", clang_getCString(cc_filename)); auto num_args = clang_CompileCommand_getNumArgs(compile_command); @@ -84,7 +85,7 @@ CXTranslationUnit compilation_database::parse_translation_unit( arguments = (char **)malloc(sizeof(char *) * num_args); for (j = 0; j < num_args; ++j) { CXString arg = clang_CompileCommand_getArg(compile_command, j); - spdlog::debug("Processing argument: {}", clang_getCString(arg)); + LOG_DBG("Processing argument: {}", clang_getCString(arg)); arguments[j] = strdup(clang_getCString(arg)); clang_disposeString(arg); } diff --git a/src/cx/util.h b/src/cx/util.h index aaf165af..b7aa7401 100644 --- a/src/cx/util.h +++ b/src/cx/util.h @@ -56,7 +56,6 @@ std::string ns(const cppast::cpp_entity &e); std::string ns(const cppast::cpp_type &t, const cppast::cpp_entity_index &idx); bool is_inside_class(const cppast::cpp_entity &e); - } // namespace util } // namespace cx } // namespace clanguml diff --git a/src/main.cc b/src/main.cc index 247adbf4..1ace8c53 100644 --- a/src/main.cc +++ b/src/main.cc @@ -21,7 +21,9 @@ #include "class_diagram/generators/plantuml/class_diagram_generator.h" #include "config/config.h" #include "cx/compilation_database.h" +#include "package_diagram/generators/plantuml/package_diagram_generator.h" #include "sequence_diagram/generators/plantuml/sequence_diagram_generator.h" + #include "util/util.h" #include @@ -79,6 +81,7 @@ int main(int argc, const char *argv[]) continue; using clanguml::config::class_diagram; + using clanguml::config::package_diagram; using clanguml::config::sequence_diagram; std::filesystem::path path{"puml/" + name + ".puml"}; @@ -103,6 +106,15 @@ int main(int argc, const char *argv[]) dynamic_cast(*diagram), model); } + else if (std::dynamic_pointer_cast(diagram)) { + auto model = + clanguml::package_diagram::generators::plantuml::generate( + db, name, dynamic_cast(*diagram)); + + ofs << clanguml::package_diagram::generators::plantuml::generator( + dynamic_cast(*diagram), + model); + } ofs.close(); } diff --git a/src/package_diagram/generators/plantuml/package_diagram_generator.cc b/src/package_diagram/generators/plantuml/package_diagram_generator.cc new file mode 100644 index 00000000..6aa4b146 --- /dev/null +++ b/src/package_diagram/generators/plantuml/package_diagram_generator.cc @@ -0,0 +1,199 @@ +/** + * src/class_diagram/generators/plantuml/class_diagram_generator.cc + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "package_diagram_generator.h" + +#include "util/error.h" + +namespace clanguml::package_diagram::generators::plantuml { + +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); +} + +generator::generator( + clanguml::config::package_diagram &config, diagram_model &model) + : m_config(config) + , m_model(model) +{ +} + +std::string generator::to_string(relationship_t r, std::string style) const +{ + switch (r) { + case relationship_t::kOwnership: + case relationship_t::kComposition: + return style.empty() ? "*--" : fmt::format("*-[{}]-", style); + case relationship_t::kAggregation: + return style.empty() ? "o--" : fmt::format("o-[{}]-", style); + case relationship_t::kContainment: + return style.empty() ? "--+" : fmt::format("-[{}]-+", style); + case relationship_t::kAssociation: + return style.empty() ? "-->" : fmt::format("-[{}]->", style); + case relationship_t::kInstantiation: + return style.empty() ? "..|>" : fmt::format(".[{}].|>", style); + case relationship_t::kFriendship: + return style.empty() ? "<.." : fmt::format("<.[{}].", style); + case relationship_t::kDependency: + return style.empty() ? "..>" : fmt::format(".[{}].>", style); + default: + return ""; + } +} + +std::string generator::name(relationship_t r) const +{ + switch (r) { + case relationship_t::kOwnership: + case relationship_t::kComposition: + return "composition"; + case relationship_t::kAggregation: + return "aggregation"; + case relationship_t::kContainment: + return "containment"; + case relationship_t::kAssociation: + return "association"; + case relationship_t::kDependency: + return "dependency"; + default: + return "unknown"; + } +} + +void generator::generate(const package &p, std::ostream &ostr) const +{ + const auto uns = m_config.using_namespace; + + ostr << "component [" << p.name() << "] as " << p.alias(); + + if (!p.style().empty()) + ostr << " " << p.style(); + + ostr << " {" << '\n'; + + // C++17 cannot figure out to use cbegin/cend in a for-range loop on const + // collection... ¯\_(ツ)_/¯ + for (auto subpackage = p.cbegin(); subpackage != p.cend(); subpackage++) { + generate(**subpackage, ostr); + } + + ostr << "}" << '\n'; + + // + // Process notes + // + for (auto decorator : p.decorators()) { + auto note = std::dynamic_pointer_cast(decorator); + if (note && note->applies_to_diagram(m_config.name)) { + ostr << "note " << note->position << " of " << p.alias() << '\n' + << note->text << '\n' + << "end note\n"; + } + } + // + // // Print relationships + // ostr << all_relations_str.str(); +} + +void generator::generate(std::ostream &ostr) const +{ + ostr << "@startuml" << '\n'; + + // Process aliases in any of the puml directives + for (const auto &b : m_config.puml.before) { + std::string note{b}; + std::tuple alias_match; + while (util::find_element_alias(note, alias_match)) { + auto alias = m_model.to_alias(ns_relative( + m_config.using_namespace, std::get<0>(alias_match))); + note.replace( + std::get<1>(alias_match), std::get<2>(alias_match), alias); + } + ostr << note << '\n'; + } + + if (m_config.should_include_entities("packages")) { + for (const auto &p : m_model) { + generate(*p, ostr); + ostr << '\n'; + } + } + + // Process aliases in any of the puml directives + for (const auto &b : m_config.puml.after) { + std::string note{b}; + std::tuple alias_match; + while (util::find_element_alias(note, alias_match)) { + auto full_name = + fmt::format("{}::{}", fmt::join(m_config.using_namespace, "::"), + ns_relative( + m_config.using_namespace, std::get<0>(alias_match))); + + auto alias = m_model.to_alias(full_name); + note.replace( + std::get<1>(alias_match), std::get<2>(alias_match), alias); + } + ostr << note << '\n'; + } + + ostr << "@enduml" << '\n'; +} + +std::ostream &operator<<(std::ostream &os, const generator &g) +{ + g.generate(os); + return os; +} + +clanguml::package_diagram::model::diagram generate( + cppast::libclang_compilation_database &db, const std::string &name, + clanguml::config::package_diagram &diagram) +{ + LOG_INFO("Generating package diagram {}.puml", name); + clanguml::package_diagram::model::diagram d; + d.set_name(name); + + // Get all translation units matching the glob from diagram + // configuration + std::vector translation_units{}; + for (const auto &g : diagram.glob) { + LOG_DBG("Processing glob: {}", g); + const auto matches = glob::rglob(g); + std::copy(matches.begin(), matches.end(), + std::back_inserter(translation_units)); + } + + cppast::cpp_entity_index idx; + cppast::simple_file_parser parser{ + type_safe::ref(idx)}; + + // Process all matching translation units + clanguml::package_diagram::visitor::translation_unit_visitor ctx( + idx, d, diagram); + cppast::parse_files(parser, translation_units, db); + for (auto &file : parser.files()) + ctx(file); + + return d; +} + +} diff --git a/src/package_diagram/generators/plantuml/package_diagram_generator.h b/src/package_diagram/generators/plantuml/package_diagram_generator.h new file mode 100644 index 00000000..5a5284be --- /dev/null +++ b/src/package_diagram/generators/plantuml/package_diagram_generator.h @@ -0,0 +1,79 @@ +/** + * src/package_diagram/generators/plantuml/package_diagram_generator.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "package_diagram/model/package.h" +#include "package_diagram/visitor/translation_unit_visitor.h" +#include "common/model/relationship.h" +#include "config/config.h" +#include "cx/compilation_database.h" +#include "package_diagram/model/diagram.h" +#include "util/util.h" + +#include +#include +#include + +#include +#include +#include +#include + +namespace clanguml { +namespace package_diagram { +namespace generators { +namespace plantuml { + +using diagram_config = clanguml::package_diagram::model::diagram; +using diagram_model = clanguml::package_diagram::model::diagram; +using clanguml::common::model::relationship_t; +using clanguml::common::model::scope_t; +using clanguml::package_diagram::model::package; +using namespace clanguml::util; + +std::string relative_to(std::string n, std::string c); + +class generator { +public: + generator(clanguml::config::package_diagram &config, diagram_model &model); + + std::string to_string(relationship_t r, std::string style = "") const; + + std::string name(relationship_t r) const; + + void generate_alias(const package &c, std::ostream &ostr) const; + + void generate(const package &e, std::ostream &ostr) const; + + void generate(std::ostream &ostr) const; + + friend std::ostream &operator<<(std::ostream &os, const generator &g); + +private: + clanguml::config::package_diagram &m_config; + diagram_model &m_model; +}; + +clanguml::package_diagram::model::diagram generate( + cppast::libclang_compilation_database &db, const std::string &name, + clanguml::config::package_diagram &diagram); + +} +} +} +} diff --git a/src/package_diagram/model/diagram.cc b/src/package_diagram/model/diagram.cc new file mode 100644 index 00000000..1ec49861 --- /dev/null +++ b/src/package_diagram/model/diagram.cc @@ -0,0 +1,48 @@ +/** + * src/package_diagram/model/diagram.cc + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "diagram.h" + +#include "util/error.h" +#include "util/util.h" + +namespace clanguml::package_diagram::model { + +std::string diagram::name() const { return name_; } + +void diagram::set_name(const std::string &name) { name_ = name; } + +std::string diagram::to_alias(const std::string &full_name) const +{ + LOG_DBG("Looking for alias for {}", full_name); + + auto fn = util::split(full_name, "::"); + + if (fn.empty()) + throw error::uml_alias_missing( + fmt::format("Missing alias for '{}'", full_name)); + + auto package = get_package(fn); + + if (!package) + throw error::uml_alias_missing( + fmt::format("Missing alias for '{}'", full_name)); + + return package.value().alias(); +} +} diff --git a/src/package_diagram/model/diagram.h b/src/package_diagram/model/diagram.h new file mode 100644 index 00000000..7a18f8ea --- /dev/null +++ b/src/package_diagram/model/diagram.h @@ -0,0 +1,42 @@ +/** + * src/package_diagram/model/diagram.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "package.h" + +#include + +#include +#include + +namespace clanguml::package_diagram::model { + +class diagram : public detail::package_trait { +public: + std::string name() const; + + void set_name(const std::string &name); + + std::string to_alias(const std::string &full_name) const; + +private: + std::string name_; + + std::vector> packages_; +}; +} diff --git a/src/package_diagram/model/package.cc b/src/package_diagram/model/package.cc new file mode 100644 index 00000000..bb5cbb9c --- /dev/null +++ b/src/package_diagram/model/package.cc @@ -0,0 +1,50 @@ +/** + * src/package_diagram/model/class.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "package.h" + +#include "util/util.h" + +#include + +namespace clanguml::package_diagram::model { +package::package(const std::vector &using_namespaces) + : element{using_namespaces} +{ +} + +std::string package::full_name(bool relative) const +{ + auto fn = get_namespace(); + auto ns = using_namespaces(); + + if (relative && (fn.size() >= ns.size())) { + if(starts_with(fn, ns)) + fn = std::vector(fn.begin() + ns.size(), fn.end()); + } + + fn.push_back(name()); + + return fmt::format("{}", fmt::join(fn, "::")); +} + +bool operator==(const package &l, const package &r) +{ + return l.full_name(false) == r.full_name(false); +} +} \ No newline at end of file diff --git a/src/package_diagram/model/package.h b/src/package_diagram/model/package.h new file mode 100644 index 00000000..01cfdd50 --- /dev/null +++ b/src/package_diagram/model/package.h @@ -0,0 +1,121 @@ +/** + * src/package_diagram/model/class.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "util/util.h" +#include "common/model/element.h" +#include "common/model/stylable_element.h" + +#include +#include + +#include +#include + +namespace clanguml::package_diagram::model { + +namespace detail { +template class Container, + typename Ptr = std::unique_ptr> +class package_trait { +public: + void add_package(std::unique_ptr p) + { + packages_.emplace_back(std::move(p)); + } + + void add_package(std::vector path, std::unique_ptr p) + { + assert(p); + + LOG_DBG( + "Adding package {} at path '{}'", p->name(), fmt::join(path, "::")); + + if (path.empty()) { + add_package(std::move(p)); + return; + } + + auto parent = get_package(path); + + if (parent) + parent.value().add_package(std::move(p)); + else + spdlog::error( + "No parent package found at: {}", fmt::join(path, "::")); + } + + type_safe::optional_ref get_package(std::vector path) const + { + LOG_DBG("Getting package at path: {}", fmt::join(path, "::")); + + if (path.empty() || !has_package(path.at(0))) + return {}; + + auto p = get_package(path.at(0)); + if (path.size() == 1) + return p; + + return p.value().get_package( + std::vector(path.begin() + 1, path.end())); + } + + type_safe::optional_ref get_package(const std::string &name) const + { + auto it = std::find_if(packages_.cbegin(), packages_.cend(), + [&](const auto &p) { return name == p->name(); }); + + if (it == packages_.end()) + return {}; + + assert(it->get() != nullptr); + + return type_safe::ref(*(it->get())); + } + + bool has_package(const std::string &name) const + { + return std::find_if(packages_.cbegin(), packages_.cend(), + [&](const auto &p) { return name == p->name(); }) != + packages_.end(); + } + + typedef typename Container::iterator iterator; + typedef typename Container::const_iterator const_iterator; + + inline iterator begin() noexcept { return packages_.begin(); } + inline const_iterator cbegin() const noexcept { return packages_.cbegin(); } + inline iterator end() noexcept { return packages_.end(); } + inline const_iterator cend() const noexcept { return packages_.cend(); } + +protected: + Container> packages_; +}; +} + +class package : public common::model::element, + public common::model::stylable_element, + public detail::package_trait { +public: + package(const std::vector &using_namespaces); + + std::string full_name(bool relative) const override; + + friend bool operator==(const package &l, const package &r); +}; +} diff --git a/src/package_diagram/visitor/element_visitor_context.cc b/src/package_diagram/visitor/element_visitor_context.cc new file mode 100644 index 00000000..56f3a2ac --- /dev/null +++ b/src/package_diagram/visitor/element_visitor_context.cc @@ -0,0 +1,43 @@ +/** + * src/package_diagram/model/visitor/element_visitor_context.cc + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "element_visitor_context.h" + +#include "translation_unit_context.h" + +namespace clanguml::package_diagram::visitor { + +template +element_visitor_context::element_visitor_context( + clanguml::package_diagram::model::diagram &diagram, T &element) + : element_{element} + , diagram_{diagram} +{ +} + +template T &element_visitor_context::element() +{ + return element_; +} + +template +clanguml::package_diagram::model::diagram &element_visitor_context::diagram() +{ + return diagram_; +} +} diff --git a/src/package_diagram/visitor/element_visitor_context.h b/src/package_diagram/visitor/element_visitor_context.h new file mode 100644 index 00000000..bcf7650e --- /dev/null +++ b/src/package_diagram/visitor/element_visitor_context.h @@ -0,0 +1,42 @@ +/** + * src/package_diagram/model/visitor/element_visitor_context.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "package_diagram/model/diagram.h" + +namespace clanguml::package_diagram::visitor { + +class translation_unit_context; + +template class element_visitor_context { +public: + element_visitor_context( + clanguml::package_diagram::model::diagram &diagram, T &element); + + T &element(); + + clanguml::package_diagram::model::diagram &diagram(); + +private: + translation_unit_context *ctx_; + + T &element_; + clanguml::package_diagram::model::diagram &diagram_; +}; + +} diff --git a/src/package_diagram/visitor/translation_unit_context.cc b/src/package_diagram/visitor/translation_unit_context.cc new file mode 100644 index 00000000..37eb53c2 --- /dev/null +++ b/src/package_diagram/visitor/translation_unit_context.cc @@ -0,0 +1,136 @@ +/** + * src/package_diagram/visitor/translation_unit_context.cc + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "translation_unit_context.h" + +#include "cx/util.h" + +namespace clanguml::package_diagram::visitor { + +translation_unit_context::translation_unit_context( + cppast::cpp_entity_index &idx, + clanguml::package_diagram::model::diagram &diagram, + const clanguml::config::package_diagram &config) + : entity_index_{idx} + , diagram_{diagram} + , config_{config} +{ +} + +bool translation_unit_context::has_type_alias( + const std::string &full_name) const +{ + bool res = alias_index_.find(full_name) != alias_index_.end(); + + LOG_DBG("Alias {} {} found in index", full_name, res ? "" : "not"); + + return res; +} + +void translation_unit_context::add_type_alias(const std::string &full_name, + type_safe::object_ref &&ref) +{ + if (!has_type_alias(full_name)) { + LOG_DBG("Stored type alias: {} -> {} ", full_name, + cppast::to_string(ref.get())); + + alias_index_.emplace(full_name, std::move(ref)); + } +} + +type_safe::object_ref +translation_unit_context::get_type_alias(const std::string &full_name) const +{ + assert(has_type_alias(full_name)); + + return alias_index_.at(full_name); +} + +type_safe::object_ref +translation_unit_context::get_type_alias_final(const cppast::cpp_type &t) const +{ + const auto type_full_name = + cx::util::full_name(cppast::remove_cv(t), entity_index_, false); + + if (has_type_alias(type_full_name)) { + return get_type_alias_final(alias_index_.at(type_full_name).get()); + } + + return type_safe::ref(t); +} + +bool translation_unit_context::has_type_alias_template( + const std::string &full_name) const +{ + bool res = + alias_template_index_.find(full_name) != alias_template_index_.end(); + + LOG_DBG("Alias template {} {} found in index", full_name, res ? "" : "not"); + + return res; +} + +void translation_unit_context::add_type_alias_template( + const std::string &full_name, + type_safe::object_ref &&ref) +{ + if (!has_type_alias_template(full_name)) { + LOG_DBG("Stored type alias template for: {} ", full_name); + + alias_template_index_.emplace(full_name, std::move(ref)); + } +} + +type_safe::object_ref +translation_unit_context::get_type_alias_template( + const std::string &full_name) const +{ + assert(has_type_alias_template(full_name)); + + return alias_template_index_.at(full_name); +} + +void translation_unit_context::push_namespace(const std::string &ns) +{ + namespace_.push_back(ns); +} + +void translation_unit_context::pop_namespace() { namespace_.pop_back(); } + +const std::vector &translation_unit_context::get_namespace() const +{ + return namespace_; +} + +const cppast::cpp_entity_index &translation_unit_context::entity_index() const +{ + return entity_index_; +} + +const clanguml::config::package_diagram & +translation_unit_context::config() const +{ + return config_; +} + +clanguml::package_diagram::model::diagram &translation_unit_context::diagram() +{ + return diagram_; +} + +} diff --git a/src/package_diagram/visitor/translation_unit_context.h b/src/package_diagram/visitor/translation_unit_context.h new file mode 100644 index 00000000..2478f2b2 --- /dev/null +++ b/src/package_diagram/visitor/translation_unit_context.h @@ -0,0 +1,88 @@ +/** + * src/package_diagram/visitor/translation_unit_context.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "config/config.h" +#include "package_diagram/model/diagram.h" + +#include +#include +#include + +namespace clanguml::package_diagram::visitor { + +class translation_unit_context { +public: + translation_unit_context(cppast::cpp_entity_index &idx, + clanguml::package_diagram::model::diagram &diagram, + const clanguml::config::package_diagram &config); + + bool has_type_alias(const std::string &full_name) const; + + void add_type_alias(const std::string &full_name, + type_safe::object_ref &&ref); + + type_safe::object_ref get_type_alias( + const std::string &full_name) const; + + type_safe::object_ref get_type_alias_final( + const cppast::cpp_type &t) const; + + bool has_type_alias_template(const std::string &full_name) const; + + void add_type_alias_template(const std::string &full_name, + type_safe::object_ref &&ref); + + type_safe::object_ref get_type_alias_template( + const std::string &full_name) const; + + void push_namespace(const std::string &ns); + + void pop_namespace(); + + const std::vector &get_namespace() const; + + const cppast::cpp_entity_index &entity_index() const; + + const clanguml::config::package_diagram &config() const; + + clanguml::package_diagram::model::diagram &diagram(); + +private: + // Current visitor namespace + std::vector namespace_; + + // Reference to the cppast entity index + cppast::cpp_entity_index &entity_index_; + + // Reference to the output diagram model + clanguml::package_diagram::model::diagram &diagram_; + + // Reference to class diagram config + const clanguml::config::package_diagram &config_; + + // Map of discovered aliases (declared with 'using' keyword) + std::map> + alias_index_; + + // Map of discovered template aliases (declared with 'using' keyword) + std::map> + alias_template_index_; +}; + +} diff --git a/src/package_diagram/visitor/translation_unit_visitor.cc b/src/package_diagram/visitor/translation_unit_visitor.cc new file mode 100644 index 00000000..2c5b660e --- /dev/null +++ b/src/package_diagram/visitor/translation_unit_visitor.cc @@ -0,0 +1,352 @@ +/** + * src/package_diagram/visitor/translation_unit_visitor.cc + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "translation_unit_visitor.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace clanguml::package_diagram::visitor { + +using clanguml::class_diagram::model::type_alias; +using clanguml::common::model::access_t; +using clanguml::common::model::relationship; +using clanguml::common::model::relationship_t; +using clanguml::common::model::scope_t; +using clanguml::package_diagram::model::diagram; +using clanguml::package_diagram::model::package; + +namespace detail { +scope_t cpp_access_specifier_to_scope( + cppast::cpp_access_specifier_kind access_specifier) +{ + scope_t scope = scope_t::kPublic; + switch (access_specifier) { + case cppast::cpp_access_specifier_kind::cpp_public: + scope = scope_t::kPublic; + break; + case cppast::cpp_access_specifier_kind::cpp_private: + scope = scope_t::kPrivate; + break; + case cppast::cpp_access_specifier_kind::cpp_protected: + scope = scope_t::kProtected; + break; + default: + break; + } + + return scope; +} +} + +translation_unit_visitor::translation_unit_visitor( + cppast::cpp_entity_index &idx, + clanguml::package_diagram::model::diagram &diagram, + const clanguml::config::package_diagram &config) + : ctx{idx, diagram, config} +{ +} + +void translation_unit_visitor::operator()(const cppast::cpp_entity &file) +{ + cppast::visit(file, + [&, this](const cppast::cpp_entity &e, cppast::visitor_info info) { + if (e.kind() == cppast::cpp_entity_kind::namespace_t) { + if (info.event == + cppast::visitor_info::container_entity_enter) { + LOG_DBG("========== Visiting '{}' - {}", e.name(), + cppast::to_string(e.kind())); + + const auto &ns_declaration = + static_cast(e); + if (!ns_declaration.is_anonymous() && + !ns_declaration.is_inline()) { + + auto p = std::make_unique( + ctx.config().using_namespace); + p->set_name(e.name()); + p->set_namespace(ctx.get_namespace()); + ctx.diagram().add_package( + ctx.get_namespace(), std::move(p)); + + ctx.push_namespace(e.name()); + } + } + else { + LOG_DBG("========== Leaving '{}' - {}", e.name(), + cppast::to_string(e.kind())); + + const auto &ns_declaration = + static_cast(e); + if (!ns_declaration.is_anonymous() && + !ns_declaration.is_inline()) + ctx.pop_namespace(); + } + } + else if (e.kind() == + cppast::cpp_entity_kind::class_template_specialization_t) { + LOG_DBG("========== Visiting '{}' - {}", + cx::util::full_name(ctx.get_namespace(), e), + cppast::to_string(e.kind())); + + auto &tspec = static_cast< + const cppast::cpp_class_template_specialization &>(e); + + process_class_declaration( + tspec.class_(), type_safe::ref(tspec)); + } + else if (e.kind() == cppast::cpp_entity_kind::class_t) { + LOG_DBG("========== Visiting '{}' - {}", + cx::util::full_name(ctx.get_namespace(), e), + cppast::to_string(e.kind())); + + auto &cls = static_cast(e); + if (cppast::get_definition(ctx.entity_index(), cls)) { + auto &clsdef = static_cast( + cppast::get_definition(ctx.entity_index(), cls) + .value()); + if (&cls != &clsdef) { + LOG_DBG("Forward declaration of class {} - skipping...", + cls.name()); + return; + } + } + + if (ctx.config().should_include( + cx::util::fully_prefixed(ctx.get_namespace(), cls))) + process_class_declaration(cls); + } + // else if (e.kind() == cppast::cpp_entity_kind::enum_t) + // { + // LOG_DBG("========== Visiting '{}' - {}", + // cx::util::full_name(ctx.get_namespace(), e), + // cppast::to_string(e.kind())); + // + // auto &enm = static_cast(e); + // + // if (ctx.config().should_include( + // cx::util::fully_prefixed(ctx.get_namespace(), + // enm))) + // process_enum_declaration(enm); + // } + else if (e.kind() == cppast::cpp_entity_kind::type_alias_t) { + LOG_DBG("========== Visiting '{}' - {}", + cx::util::full_name(ctx.get_namespace(), e), + cppast::to_string(e.kind())); + + auto &ta = static_cast(e); + type_alias t; + t.set_alias(cx::util::full_name(ctx.get_namespace(), ta)); + t.set_underlying_type(cx::util::full_name(ta.underlying_type(), + ctx.entity_index(), cx::util::is_inside_class(e))); + + ctx.add_type_alias(cx::util::full_name(ctx.get_namespace(), ta), + type_safe::ref(ta.underlying_type())); + + // ctx.diagram().add_type_alias(std::move(t)); + } + else if (e.kind() == cppast::cpp_entity_kind::alias_template_t) { + LOG_DBG("========== Visiting '{}' - {}", + cx::util::full_name(ctx.get_namespace(), e), + cppast::to_string(e.kind())); + + auto &at = static_cast(e); + + // if (at.type_alias().underlying_type().kind() + // == + // cppast::cpp_type_kind::unexposed_t) { + // LOG_WARN("Template alias has unexposed + // underlying type: {}", + // static_cast( + // at.type_alias().underlying_type()) + // .name()); + // } + // else { + // class_ tinst = + // build_template_instantiation(static_cast< + // const + // cppast::cpp_template_instantiation_type + // &>( + // at.type_alias().underlying_type())); + // + // ctx.diagram().add_class(std::move(tinst)); + // } + } + }); +} + +void translation_unit_visitor::process_class_declaration( + const cppast::cpp_class &cls, + type_safe::optional_ref tspec) +{ + + return; + /* + class_ c{ctx.config().using_namespace}; + c.is_struct(cls.class_kind() == cppast::cpp_class_kind::struct_t); + c.set_name(cx::util::full_name(ctx.get_namespace(), cls)); + + if (cls.comment().has_value()) + c.add_decorators(decorators::parse(cls.comment().value())); + + cppast::cpp_access_specifier_kind last_access_specifier = + cppast::cpp_access_specifier_kind::cpp_private; + + // Process class documentation comment + if (cppast::is_templated(cls)) { + if (cls.parent().value().comment().has_value()) + c.add_decorators( + decorators::parse(cls.parent().value().comment().value())); + } + else { + if (cls.comment().has_value()) + c.add_decorators(decorators::parse(cls.comment().value())); + } + + if (c.skip()) + return; + + c.set_style(c.style_spec()); + + // Process class child entities + if (c.is_struct()) + last_access_specifier = cppast::cpp_access_specifier_kind::cpp_public; + + for (auto &child : cls) { + if (child.kind() == cppast::cpp_entity_kind::access_specifier_t) { + auto &as = static_cast(child); + last_access_specifier = as.access_specifier(); + } + else if (child.kind() == cppast::cpp_entity_kind::member_variable_t) { + auto &mv = static_cast(child); + process_field(mv, c, last_access_specifier); + } + else if (child.kind() == cppast::cpp_entity_kind::variable_t) { + auto &mv = static_cast(child); + process_static_field(mv, c, last_access_specifier); + } + else if (child.kind() == cppast::cpp_entity_kind::member_function_t) { + auto &mf = static_cast(child); + process_method(mf, c, last_access_specifier); + } + else if (child.kind() == cppast::cpp_entity_kind::function_t) { + auto &mf = static_cast(child); + process_static_method(mf, c, last_access_specifier); + } + else if (child.kind() == cppast::cpp_entity_kind::function_template_t) { + auto &tm = + static_cast(child); + process_template_method(tm, c, last_access_specifier); + } + else if (child.kind() == cppast::cpp_entity_kind::constructor_t) { + auto &mc = static_cast(child); + process_constructor(mc, c, last_access_specifier); + } + else if (child.kind() == cppast::cpp_entity_kind::destructor_t) { + auto &mc = static_cast(child); + process_destructor(mc, c, last_access_specifier); + } + else if (child.kind() == cppast::cpp_entity_kind::enum_t) { + auto &en = static_cast(child); + if (en.name().empty()) { + // Here we only want to handle anonymous enums, regular nested + // enums are handled in the file-level visitor + process_anonymous_enum(en, c, last_access_specifier); + } + } + else if (child.kind() == cppast::cpp_entity_kind::friend_t) { + auto &fr = static_cast(child); + + LOG_DBG("Found friend declaration: {}, {}", child.name(), + child.scope_name() ? child.scope_name().value().name() + : ""); + + process_friend(fr, c); + } + else if (cppast::is_friended(child)) { + auto &fr = + static_cast(child.parent().value()); + + LOG_DBG("Found friend template: {}", child.name()); + + process_friend(fr, c); + } + else { + LOG_DBG("Found some other class child: {} ({})", child.name(), + cppast::to_string(child.kind())); + } + } + + // Process class bases + for (auto &base : cls.bases()) { + class_parent cp; + cp.set_name( + clanguml::cx::util::fully_prefixed(ctx.get_namespace(), base)); + cp.is_virtual(base.is_virtual()); + + switch (base.access_specifier()) { + case cppast::cpp_access_specifier_kind::cpp_private: + cp.set_access(access_t::kPrivate); + break; + case cppast::cpp_access_specifier_kind::cpp_public: + cp.set_access(access_t::kPublic); + break; + case cppast::cpp_access_specifier_kind::cpp_protected: + cp.set_access(access_t::kProtected); + break; + default: + cp.set_access(access_t::kPublic); + } + + LOG_DBG("Found base class {} for class {}", cp.name(), c.name()); + + c.add_parent(std::move(cp)); + } + + ctx.diagram().add_class(std::move(c)); + */ +} + +const cppast::cpp_type &translation_unit_visitor::resolve_alias( + const cppast::cpp_type &type) +{ + const auto &raw_type = cppast::remove_cv(cx::util::unreferenced(type)); + const auto type_full_name = + cx::util::full_name(raw_type, ctx.entity_index(), false); + if (ctx.has_type_alias(type_full_name)) { + return ctx.get_type_alias_final(raw_type).get(); + } + + return type; +} +} diff --git a/src/package_diagram/visitor/translation_unit_visitor.h b/src/package_diagram/visitor/translation_unit_visitor.h new file mode 100644 index 00000000..b6b0db57 --- /dev/null +++ b/src/package_diagram/visitor/translation_unit_visitor.h @@ -0,0 +1,66 @@ +/** + * src/package_diagram/visitor/translation_unit_visitor.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "package_diagram/model/diagram.h" +#include "package_diagram/visitor/translation_unit_context.h" +#include "config/config.h" +#include "cx/cursor.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace clanguml::package_diagram::visitor { + +class translation_unit_visitor { +public: + translation_unit_visitor(cppast::cpp_entity_index &idx, + clanguml::package_diagram::model::diagram &diagram, + const clanguml::config::package_diagram &config); + + void operator()(const cppast::cpp_entity &file); + + void process_class_declaration(const cppast::cpp_class &cls, + type_safe::optional_ref + tspec = nullptr); + +private: + /** + * Try to resolve a type instance into a type referenced through an alias. + * If t does not represent an alias, returns t. + */ + const cppast::cpp_type &resolve_alias(const cppast::cpp_type &t); + + // ctx allows to track current visitor context, e.g. current namespace + translation_unit_context ctx; +}; +} diff --git a/src/util/util.h b/src/util/util.h index 7324e762..ed0814bd 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -115,4 +115,34 @@ bool find_element_alias( bool replace_all( std::string &input, std::string pattern, std::string replace_with); } + +/** + * @brief Appends a vector to a vector. + * + * @tparam T + * @param l + * @param r + */ +template void append(std::vector &l, const std::vector &r) +{ + l.insert(l.end(), r.begin(), r.end()); +} + +/** + * @brief Checks if vector starts with a prefix. + * + * @tparam T + * @param col + * @param prefix + * @return + */ +template +bool starts_with(const std::vector &col, const std::vector &prefix) +{ + if(prefix.size() > col.size()) + return false; + + return std::vector(prefix.begin(), prefix.end()) == + std::vector(col.begin(), col.begin() + prefix.size()); +} } diff --git a/tests/t30001/.clang-uml b/tests/t30001/.clang-uml new file mode 100644 index 00000000..a4453e8c --- /dev/null +++ b/tests/t30001/.clang-uml @@ -0,0 +1,20 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t30001_package: + type: package + glob: + - ../../tests/t30001/t30001.cc + include: + namespaces: + - clanguml::t30001 + exclude: + namespaces: + - clanguml::t30001::detail + using_namespace: + - clanguml::t30001 + plantuml: + before: + - "' t30001 test package diagram" + after: + - "note left of @A(A::AA::AAA): A AAA note..." \ No newline at end of file diff --git a/tests/t30001/t30001.cc b/tests/t30001/t30001.cc new file mode 100644 index 00000000..98e901ce --- /dev/null +++ b/tests/t30001/t30001.cc @@ -0,0 +1,25 @@ + +namespace clanguml { +namespace t30001 { +namespace A { +namespace AA { +namespace AAA { +} // namespace AAA +namespace BBB { +} // namespace BBB +} // namespace AA +namespace BB { +} // namespace BB +} // namespace A +namespace B { +namespace AA { +namespace AAA { +} // namespace AAA +namespace BBB { +} // namespace BBB +} // namespace AA +namespace BB { +} // namespace BB +} // namespace B +} +} diff --git a/tests/t30001/test_case.h b/tests/t30001/test_case.h new file mode 100644 index 00000000..7c5af2af --- /dev/null +++ b/tests/t30001/test_case.h @@ -0,0 +1,55 @@ +/** + * tests/t30001/test_case.cc + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +TEST_CASE("t30001", "[test-case][package]") +{ + auto [config, db] = load_config("t30001"); + + auto diagram = config.diagrams["t30001_package"]; + + REQUIRE(diagram->include.namespaces.size() == 1); + REQUIRE_THAT(diagram->include.namespaces, + VectorContains(std::string{"clanguml::t30001"})); + + REQUIRE(diagram->exclude.namespaces.size() == 1); + REQUIRE_THAT(diagram->exclude.namespaces, + VectorContains(std::string{"clanguml::t30001::detail"})); + + REQUIRE(diagram->should_include("clanguml::t30001::A")); + REQUIRE(!diagram->should_include("clanguml::t30001::detail::C")); + REQUIRE(!diagram->should_include("std::vector")); + + REQUIRE(diagram->name == "t30001_package"); + + auto model = generate_package_diagram(db, diagram); + + REQUIRE(model.name() == "t30001_package"); + + auto puml = generate_package_puml(diagram, model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + REQUIRE_THAT(puml, Contains("component [A]")); + REQUIRE_THAT(puml, Contains("component [AA]")); + REQUIRE_THAT(puml, Contains("component [AAA]")); + + save_puml( + "./" + config.output_directory + "/" + diagram->name + ".puml", puml); +} diff --git a/tests/test_cases.cc b/tests/test_cases.cc index 720709d2..a2cc142b 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -64,6 +64,18 @@ clanguml::class_diagram::model::diagram generate_class_diagram( return diagram_model; } +clanguml::package_diagram::model::diagram generate_package_diagram( + cppast::libclang_compilation_database &db, + std::shared_ptr diagram) +{ + auto diagram_model = + clanguml::package_diagram::generators::plantuml::generate(db, + diagram->name, + dynamic_cast(*diagram)); + + return diagram_model; +} + std::string generate_sequence_puml( std::shared_ptr config, clanguml::sequence_diagram::model::diagram &model) @@ -92,6 +104,20 @@ std::string generate_class_puml( return ss.str(); } +std::string generate_package_puml( + std::shared_ptr config, + clanguml::package_diagram::model::diagram &model) +{ + using namespace clanguml::package_diagram::generators::plantuml; + + std::stringstream ss; + + ss << generator( + dynamic_cast(*config), model); + + return ss.str(); +} + void save_puml(const std::string &path, const std::string &puml) { std::filesystem::path p{path}; @@ -147,6 +173,11 @@ using namespace clanguml::test::matchers; #include "t20001/test_case.h" #include "t20002/test_case.h" +// +// Package diagram tests +// +#include "t30001/test_case.h" + // // Other tests (e.g. configuration file) // diff --git a/tests/test_cases.h b/tests/test_cases.h index 3a676030..14d7f6b7 100644 --- a/tests/test_cases.h +++ b/tests/test_cases.h @@ -24,6 +24,8 @@ #include "class_diagram/visitor/translation_unit_visitor.h" #include "config/config.h" #include "cx/compilation_database.h" +#include "package_diagram/generators/plantuml/package_diagram_generator.h" +#include "package_diagram/visitor/translation_unit_visitor.h" #include "sequence_diagram/generators/plantuml/sequence_diagram_generator.h" #include "sequence_diagram/visitor/translation_unit_visitor.h" #include "util/util.h" @@ -32,6 +34,7 @@ #include "catch.h" +#include #include #include #include @@ -168,6 +171,8 @@ struct AliasMatcher { patterns.push_back("class \"" + name + "\" as "); patterns.push_back("abstract \"" + name + "\" as "); patterns.push_back("enum \"" + name + "\" as "); + patterns.push_back("component \"" + name + "\" as "); + patterns.push_back("component [" + name + "] as "); for (const auto &line : puml) { for (const auto &pattern : patterns) { @@ -396,6 +401,13 @@ ContainsMatcher IsField(std::string const &name, return ContainsMatcher( CasedString(pattern + " : " + type, caseSensitivity)); } + +ContainsMatcher IsPackage(std::string const &str, + CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes) +{ + return ContainsMatcher( + CasedString("component [" + str + "]", caseSensitivity)); +} } } }