diff --git a/src/common/model/diagram_filter.cc b/src/common/model/diagram_filter.cc index 2c60930b..57809b59 100644 --- a/src/common/model/diagram_filter.cc +++ b/src/common/model/diagram_filter.cc @@ -20,6 +20,7 @@ #include "class_diagram/model/class.h" #include "common/model/package.h" +#include "include_diagram/model/diagram.h" #include "package_diagram/model/diagram.h" namespace clanguml::common::model { @@ -47,6 +48,13 @@ const std::vector> &view( return d.packages(); } +template <> +const std::vector> & +view(const include_diagram::model::diagram &d) +{ + return d.files(); +} + template <> const type_safe::optional_ref get( const class_diagram::model::diagram &d, const std::string &full_name) @@ -61,8 +69,15 @@ const type_safe::optional_ref get( return d.get_package(full_name); } +template <> +const type_safe::optional_ref get( + const include_diagram::model::diagram &d, const std::string &full_name) +{ + return d.get_file(full_name); } +} // namespace detail + filter_visitor::filter_visitor(filter_t type) : type_{type} { @@ -124,6 +139,13 @@ tvl::value_t anyof_filter::match( [&d, &e](const auto &f) { return f->match(d, e); }); } +tvl::value_t anyof_filter::match( + const diagram &d, const common::model::source_file &e) const +{ + return tvl::any_of(filters_.begin(), filters_.end(), + [&d, &e](const auto &f) { return f->match(d, e); }); +} + namespace_filter::namespace_filter( filter_t type, std::vector namespaces) : filter_visitor{type} @@ -389,33 +411,72 @@ void diagram_filter::init_filters(const config::diagram &c) element_filters.emplace_back(std::make_unique( filter_t::kInclusive, c.include().elements)); - element_filters.emplace_back(std::make_unique( - filter_t::kInclusive, c.include().subclasses)); + if (c.type() == diagram_t::kClass) { + element_filters.emplace_back(std::make_unique( + filter_t::kInclusive, c.include().subclasses)); - element_filters.emplace_back( - std::make_unique>(filter_t::kInclusive, + element_filters.emplace_back(std::make_unique< + tree_element_filter>(filter_t::kInclusive, relationship_t::kInstantiation, c.include().specializations)); - element_filters.emplace_back( - std::make_unique>(filter_t::kInclusive, + element_filters.emplace_back(std::make_unique< + tree_element_filter>(filter_t::kInclusive, relationship_t::kDependency, c.include().dependants)); - element_filters.emplace_back(std::make_unique>( - filter_t::kInclusive, relationship_t::kDependency, - c.include().dependants)); - - element_filters.emplace_back( - std::make_unique>(filter_t::kInclusive, + element_filters.emplace_back(std::make_unique< + tree_element_filter>(filter_t::kInclusive, relationship_t::kDependency, c.include().dependencies, true)); + } + else if (c.type() == diagram_t::kPackage) { + element_filters.emplace_back(std::make_unique>( + filter_t::kInclusive, relationship_t::kDependency, + c.include().dependants)); - element_filters.emplace_back(std::make_unique>( - filter_t::kInclusive, relationship_t::kDependency, - c.include().dependencies, true)); + element_filters.emplace_back(std::make_unique>( + filter_t::kInclusive, relationship_t::kDependency, + c.include().dependencies, true)); + } + else if (c.type() == diagram_t::kInclude) { + std::vector dependants; + std::vector dependencies; + + for (auto &&path : c.include().dependants) { + std::filesystem::path dep_path{path}; + if (dep_path.is_relative()) { + dep_path = c.base_directory() / path; + dep_path = relative(dep_path, c.relative_to()); + } + + dependants.emplace_back(dep_path.lexically_normal().string()); + } + + for (auto &&path : c.include().dependencies) { + std::filesystem::path dep_path{path}; + if (dep_path.is_relative()) { + dep_path = c.base_directory() / path; + dep_path = relative(dep_path, c.relative_to()); + } + + dependencies.emplace_back(dep_path.lexically_normal().string()); + } + + element_filters.emplace_back(std::make_unique< + tree_element_filter>( + filter_t::kInclusive, relationship_t::kAssociation, + dependants)); + + element_filters.emplace_back(std::make_unique< + tree_element_filter>( + filter_t::kInclusive, relationship_t::kAssociation, + dependencies, true)); + } element_filters.emplace_back(std::make_unique( filter_t::kInclusive, c.include().context)); @@ -469,6 +530,41 @@ void diagram_filter::init_filters(const config::diagram &c) filter_t::kExclusive, relationship_t::kDependency, c.exclude().dependencies, true)); + if (c.type() == diagram_t::kInclude) { + std::vector dependants; + std::vector dependencies; + + for (auto &&path : c.exclude().dependants) { + std::filesystem::path dep_path{path}; + if (dep_path.is_relative()) { + dep_path = c.base_directory() / path; + dep_path = relative(dep_path, c.relative_to()); + } + + dependants.emplace_back(dep_path.lexically_normal().string()); + } + + for (auto &&path : c.exclude().dependencies) { + std::filesystem::path dep_path{path}; + if (dep_path.is_relative()) { + dep_path = c.base_directory() / path; + dep_path = relative(dep_path, c.relative_to()); + } + + dependencies.emplace_back(dep_path.lexically_normal().string()); + } + + add_exclusive_filter(std::make_unique< + tree_element_filter>(filter_t::kExclusive, + relationship_t::kAssociation, dependencies, true)); + + add_exclusive_filter(std::make_unique< + tree_element_filter>(filter_t::kExclusive, + relationship_t::kAssociation, dependants)); + } + add_exclusive_filter(std::make_unique( filter_t::kExclusive, c.exclude().context)); } diff --git a/src/common/model/diagram_filter.h b/src/common/model/diagram_filter.h index e837ea1e..3b90d059 100644 --- a/src/common/model/diagram_filter.h +++ b/src/common/model/diagram_filter.h @@ -25,6 +25,7 @@ #include "config/config.h" #include "cx/util.h" #include "diagram.h" +#include "include_diagram/model/diagram.h" #include "source_file.h" #include "tvl.h" @@ -79,6 +80,9 @@ struct anyof_filter : public filter_visitor { tvl::value_t match( const diagram &d, const common::model::element &e) const override; + tvl::value_t match( + const diagram &d, const common::model::source_file &e) const override; + private: std::vector> filters_; }; @@ -112,7 +116,8 @@ private: std::vector roots_; }; -template +template struct tree_element_filter : public filter_visitor { tree_element_filter(filter_t type, relationship_t relationship, std::vector roots, bool forward = false) @@ -123,7 +128,7 @@ struct tree_element_filter : public filter_visitor { { } - tvl::value_t match(const diagram &d, const element &e) const override + tvl::value_t match(const diagram &d, const MatchOverrideT &e) const override { // This filter should only be run on the completely generated diagram // model by visitor @@ -168,41 +173,92 @@ private: matching_elements_.emplace(template_ref.value()); } - auto match_tree_rel = [&, this](const auto &from, const auto &to) { - bool added_new_element{false}; + assert(!matching_elements_.empty()); - for (const auto &from_el : from) { - // Check if any of its relationships of type relationship_ - // points to an element already in the matching_elements_ - // set - for (const auto &rel : from_el->relationships()) { - if (rel.type() == relationship_) { - for (const auto &to_el : to) { - if (rel.destination() == to_el->full_name(false)) { - const auto &to_add = forward_ ? to_el : from_el; - if (matching_elements_.insert(to_add).second) - added_new_element = true; + if constexpr (std::is_same_v) { + auto match_tree_rel = [&, this](const auto &from, const auto &to) { + bool added_new_element{false}; + + for (const auto &from_el : from) { + // Check if any of its relationships of type relationship_ + // points to an element already in the matching_elements_ + // set + for (const auto &rel : from_el->relationships()) { + if (rel.type() == relationship_) { + for (const auto &to_el : to) { + auto dest = rel.destination(); + auto alias = to_el->alias(); + if (dest == alias) { + const auto &to_add = + forward_ ? to_el : from_el; + if (matching_elements_.insert(to_add) + .second) + added_new_element = true; + } } } } } - } - return added_new_element; - }; + return added_new_element; + }; - bool keep_looking{true}; - while (keep_looking) { - keep_looking = false; - if (forward_) { - if (match_tree_rel( - matching_elements_, detail::view(cd))) - keep_looking = true; + bool keep_looking{true}; + while (keep_looking) { + keep_looking = false; + if (forward_) { + if (match_tree_rel( + matching_elements_, detail::view(cd))) + keep_looking = true; + } + else { + if (match_tree_rel( + detail::view(cd), matching_elements_)) + keep_looking = true; + } } - else { - if (match_tree_rel( - detail::view(cd), matching_elements_)) - keep_looking = true; + } + else { + auto match_tree_rel = [&, this](const auto &from, const auto &to) { + bool added_new_element{false}; + + for (const auto &from_el : from) { + // Check if any of its relationships of type relationship_ + // points to an element already in the matching_elements_ + // set + for (const auto &rel : from_el->relationships()) { + if (rel.type() == relationship_) { + for (const auto &to_el : to) { + auto dest = rel.destination(); + auto to_el_fn = to_el->full_name(false); + if (dest == to_el_fn) { + const auto &to_add = + forward_ ? to_el : from_el; + if (matching_elements_.insert(to_add) + .second) + added_new_element = true; + } + } + } + } + } + + return added_new_element; + }; + + bool keep_looking{true}; + while (keep_looking) { + keep_looking = false; + if (forward_) { + if (match_tree_rel( + matching_elements_, detail::view(cd))) + keep_looking = true; + } + else { + if (match_tree_rel( + detail::view(cd), matching_elements_)) + keep_looking = true; + } } } diff --git a/src/common/model/source_file.h b/src/common/model/source_file.h index 05c4ab5c..0ba8af1d 100644 --- a/src/common/model/source_file.h +++ b/src/common/model/source_file.h @@ -129,4 +129,15 @@ template <> struct hash { } }; +template <> +struct hash> { + std::size_t operator()( + const type_safe::object_ref + &key) const + { + using clanguml::common::model::source_file; + + return std::hash{}(key.get().full_name(false)); + } +}; } diff --git a/src/include_diagram/generators/plantuml/include_diagram_generator.cc b/src/include_diagram/generators/plantuml/include_diagram_generator.cc index 4aea084d..69931a16 100644 --- a/src/include_diagram/generators/plantuml/include_diagram_generator.cc +++ b/src/include_diagram/generators/plantuml/include_diagram_generator.cc @@ -42,7 +42,11 @@ void generator::generate_relationships( } else { for (const auto &r : f.relationships()) { - if (m_model.should_include(r.type())) { + if (m_model.should_include(r.type()) && + // make sure we only generate relationships for elements + // included in the diagram + util::contains(m_generated_aliases, r.destination()) && + util::contains(m_generated_aliases, f.alias())) { ostr << f.alias() << " " << plantuml_common::to_plantuml(r.type(), r.style()) << " " << r.destination() << '\n'; @@ -63,15 +67,21 @@ void generator::generate(const source_file &f, std::ostream &ostr) const generate(dynamic_cast(*file), ostr); } ostr << "}" << '\n'; + + m_generated_aliases.emplace(f.alias()); } else { - ostr << "file \"" << f.name() << "\" as " << f.alias(); + if (m_model.should_include(f)) { + ostr << "file \"" << f.name() << "\" as " << f.alias(); - if (m_config.generate_links) { - generate_link(ostr, f); + if (m_config.generate_links) { + generate_link(ostr, f); + } + + ostr << '\n'; + + m_generated_aliases.emplace(f.alias()); } - - ostr << '\n'; } } @@ -83,7 +93,9 @@ void generator::generate(std::ostream &ostr) const // Generate files and folders for (const auto &p : m_model) { - generate(dynamic_cast(*p), ostr); + if (p->type() == common::model::source_file_t::kDirectory || + m_model.should_include(*p)) + generate(dynamic_cast(*p), ostr); } // Process file include relationships diff --git a/src/include_diagram/model/diagram.cc b/src/include_diagram/model/diagram.cc index d6a7a17a..fe42713f 100644 --- a/src/include_diagram/model/diagram.cc +++ b/src/include_diagram/model/diagram.cc @@ -25,7 +25,7 @@ namespace clanguml::include_diagram::model { common::model::diagram_t diagram::type() const { - return common::model::diagram_t::kPackage; + return common::model::diagram_t::kInclude; } type_safe::optional_ref diagram::get( @@ -94,11 +94,18 @@ std::string diagram::to_alias(const std::string &full_name) const return source_file.value().alias(); } +const std::vector< + type_safe::object_ref> & +diagram::files() const +{ + return files_; +} + } namespace clanguml::common::model { template <> -bool check_diagram_type(diagram_t t) +bool check_diagram_type(diagram_t t) { return t == diagram_t::kInclude; } diff --git a/src/include_diagram/model/diagram.h b/src/include_diagram/model/diagram.h index 21d82c1e..81082e9c 100644 --- a/src/include_diagram/model/diagram.h +++ b/src/include_diagram/model/diagram.h @@ -52,6 +52,10 @@ public: std::string to_alias(const std::string &full_name) const; + const std::vector< + type_safe::object_ref> & + files() const; + private: std::vector> files_; diff --git a/tests/t40003/.clang-uml b/tests/t40003/.clang-uml index f3c8de66..df927287 100644 --- a/tests/t40003/.clang-uml +++ b/tests/t40003/.clang-uml @@ -6,8 +6,8 @@ diagrams: # Provide the files to parse in order to look # for #include directives glob: - - ../../tests/t40003/**/*.cc - - ../../tests/t40003/**/*.h + - ../../tests/t40003/include/**/*.h + - ../../tests/t40003/src/**/*.cc # Render the paths relative to this directory relative_to: ../../tests/t40003 include: diff --git a/tests/t40003/include/dependants/t1.h b/tests/t40003/include/dependants/t1.h index 90b87ca7..95e66566 100644 --- a/tests/t40003/include/dependants/t1.h +++ b/tests/t40003/include/dependants/t1.h @@ -1,5 +1,5 @@ #pragma once namespace clanguml::t40003::dependants { -void t1() {} +void t1() { } } \ No newline at end of file diff --git a/tests/t40003/include/dependencies/t1.h b/tests/t40003/include/dependencies/t1.h index 6485c4d4..715c9465 100644 --- a/tests/t40003/include/dependencies/t1.h +++ b/tests/t40003/include/dependencies/t1.h @@ -1,5 +1,5 @@ #pragma once namespace clanguml::t40003::dependencies { -void t1() {} +void t1() { } } \ No newline at end of file diff --git a/tests/t40003/include/dependencies/t2.h b/tests/t40003/include/dependencies/t2.h index c887e1e8..367a7696 100644 --- a/tests/t40003/include/dependencies/t2.h +++ b/tests/t40003/include/dependencies/t2.h @@ -3,5 +3,5 @@ #include "t1.h" namespace clanguml::t40003::dependencies { -void t2() {t1();} +void t2() { t1(); } } \ No newline at end of file diff --git a/tests/t40003/include/dependencies/t3.h b/tests/t40003/include/dependencies/t3.h index 327a0b56..eca46e47 100644 --- a/tests/t40003/include/dependencies/t3.h +++ b/tests/t40003/include/dependencies/t3.h @@ -3,5 +3,5 @@ #include "t2.h" namespace clanguml::t40003::dependencies { -void t3() {t2();} +void t3() { t2(); } } \ No newline at end of file diff --git a/tests/t40003/include/dependencies/t5.h b/tests/t40003/include/dependencies/t5.h index 599aeabe..575d665a 100644 --- a/tests/t40003/include/dependencies/t5.h +++ b/tests/t40003/include/dependencies/t5.h @@ -1,5 +1,7 @@ #pragma once +#include "t1.h" + namespace clanguml::t40003::dependencies { -void t5() { } +void t5() { t1(); } } \ No newline at end of file diff --git a/tests/t40003/include/dependencies/t6.h b/tests/t40003/include/dependencies/t6.h new file mode 100644 index 00000000..07fd0d3e --- /dev/null +++ b/tests/t40003/include/dependencies/t6.h @@ -0,0 +1,7 @@ +#pragma once + +#include "t1.h" + +namespace clanguml::t40003::dependencies { +void t6() { t1(); } +} \ No newline at end of file diff --git a/tests/t40003/test_case.h b/tests/t40003/test_case.h index db337b60..38c2c3fd 100644 --- a/tests/t40003/test_case.h +++ b/tests/t40003/test_case.h @@ -1,48 +1,51 @@ /** -* tests/t40003/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. -*/ + * tests/t40003/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("t40003", "[test-case][package]") { - auto [config, db] = load_config("t40003"); + auto [config, db] = load_config("t40003"); - auto diagram = config.diagrams["t40003_include"]; + auto diagram = config.diagrams["t40003_include"]; - REQUIRE(diagram->name == "t40003_include"); + REQUIRE(diagram->name == "t40003_include"); - auto model = generate_include_diagram(db, diagram); + auto model = generate_include_diagram(db, diagram); - REQUIRE(model->name() == "t40003_include"); + REQUIRE(model->name() == "t40003_include"); - auto puml = generate_include_puml(diagram, *model); + auto puml = generate_include_puml(diagram, *model); - AliasMatcher _A(puml); + AliasMatcher _A(puml); - REQUIRE_THAT(puml, StartsWith("@startuml")); - REQUIRE_THAT(puml, EndsWith("@enduml\n")); + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); - REQUIRE_THAT(puml, IsFolder("dependants")); - REQUIRE_THAT(puml, IsFolder("dependencies")); + REQUIRE_THAT(puml, IsFolder("dependants")); + REQUIRE_THAT(puml, IsFolder("dependencies")); - REQUIRE_THAT(puml, !IsFile("t4.h")); - REQUIRE_THAT(puml, !IsFile("t5.h")); + REQUIRE_THAT(puml, IsFile("t1.h")); + REQUIRE_THAT(puml, IsFile("t2.h")); + REQUIRE_THAT(puml, IsFile("t3.h")); + REQUIRE_THAT(puml, !IsFile("t4.h")); + REQUIRE_THAT(puml, IsFile("t5.h")); + REQUIRE_THAT(puml, !IsFile("t6.h")); - - save_puml( - "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); }