Fixed dependants and dependencies include diagram handling

This commit is contained in:
Bartek Kryza
2022-04-23 16:31:01 +02:00
parent d7781794ec
commit cc22494c79
14 changed files with 295 additions and 97 deletions

View File

@@ -20,6 +20,7 @@
#include "class_diagram/model/class.h" #include "class_diagram/model/class.h"
#include "common/model/package.h" #include "common/model/package.h"
#include "include_diagram/model/diagram.h"
#include "package_diagram/model/diagram.h" #include "package_diagram/model/diagram.h"
namespace clanguml::common::model { namespace clanguml::common::model {
@@ -47,6 +48,13 @@ const std::vector<type_safe::object_ref<const common::model::package>> &view(
return d.packages(); return d.packages();
} }
template <>
const std::vector<type_safe::object_ref<const common::model::source_file>> &
view(const include_diagram::model::diagram &d)
{
return d.files();
}
template <> template <>
const type_safe::optional_ref<const class_diagram::model::class_> get( const type_safe::optional_ref<const class_diagram::model::class_> get(
const class_diagram::model::diagram &d, const std::string &full_name) const class_diagram::model::diagram &d, const std::string &full_name)
@@ -61,8 +69,15 @@ const type_safe::optional_ref<const common::model::package> get(
return d.get_package(full_name); return d.get_package(full_name);
} }
template <>
const type_safe::optional_ref<const common::model::source_file> 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) filter_visitor::filter_visitor(filter_t type)
: type_{type} : type_{type}
{ {
@@ -124,6 +139,13 @@ tvl::value_t anyof_filter::match(
[&d, &e](const auto &f) { return f->match(d, e); }); [&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( namespace_filter::namespace_filter(
filter_t type, std::vector<namespace_> namespaces) filter_t type, std::vector<namespace_> namespaces)
: filter_visitor{type} : filter_visitor{type}
@@ -389,33 +411,72 @@ void diagram_filter::init_filters(const config::diagram &c)
element_filters.emplace_back(std::make_unique<element_filter>( element_filters.emplace_back(std::make_unique<element_filter>(
filter_t::kInclusive, c.include().elements)); filter_t::kInclusive, c.include().elements));
element_filters.emplace_back(std::make_unique<subclass_filter>( if (c.type() == diagram_t::kClass) {
filter_t::kInclusive, c.include().subclasses)); element_filters.emplace_back(std::make_unique<subclass_filter>(
filter_t::kInclusive, c.include().subclasses));
element_filters.emplace_back( element_filters.emplace_back(std::make_unique<
std::make_unique<tree_element_filter<class_diagram::model::diagram, tree_element_filter<class_diagram::model::diagram,
class_diagram::model::class_>>(filter_t::kInclusive, class_diagram::model::class_>>(filter_t::kInclusive,
relationship_t::kInstantiation, c.include().specializations)); relationship_t::kInstantiation, c.include().specializations));
element_filters.emplace_back( element_filters.emplace_back(std::make_unique<
std::make_unique<tree_element_filter<class_diagram::model::diagram, tree_element_filter<class_diagram::model::diagram,
class_diagram::model::class_>>(filter_t::kInclusive, class_diagram::model::class_>>(filter_t::kInclusive,
relationship_t::kDependency, c.include().dependants)); relationship_t::kDependency, c.include().dependants));
element_filters.emplace_back(std::make_unique<tree_element_filter< element_filters.emplace_back(std::make_unique<
package_diagram::model::diagram, common::model::package>>( tree_element_filter<class_diagram::model::diagram,
filter_t::kInclusive, relationship_t::kDependency, class_diagram::model::class_>>(filter_t::kInclusive,
c.include().dependants));
element_filters.emplace_back(
std::make_unique<tree_element_filter<class_diagram::model::diagram,
class_diagram::model::class_>>(filter_t::kInclusive,
relationship_t::kDependency, c.include().dependencies, true)); relationship_t::kDependency, c.include().dependencies, true));
}
else if (c.type() == diagram_t::kPackage) {
element_filters.emplace_back(std::make_unique<tree_element_filter<
package_diagram::model::diagram, common::model::package>>(
filter_t::kInclusive, relationship_t::kDependency,
c.include().dependants));
element_filters.emplace_back(std::make_unique<tree_element_filter< element_filters.emplace_back(std::make_unique<tree_element_filter<
package_diagram::model::diagram, common::model::package>>( package_diagram::model::diagram, common::model::package>>(
filter_t::kInclusive, relationship_t::kDependency, filter_t::kInclusive, relationship_t::kDependency,
c.include().dependencies, true)); c.include().dependencies, true));
}
else if (c.type() == diagram_t::kInclude) {
std::vector<std::string> dependants;
std::vector<std::string> 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<include_diagram::model::diagram,
common::model::source_file, common::model::source_file>>(
filter_t::kInclusive, relationship_t::kAssociation,
dependants));
element_filters.emplace_back(std::make_unique<
tree_element_filter<include_diagram::model::diagram,
common::model::source_file, common::model::source_file>>(
filter_t::kInclusive, relationship_t::kAssociation,
dependencies, true));
}
element_filters.emplace_back(std::make_unique<context_filter>( element_filters.emplace_back(std::make_unique<context_filter>(
filter_t::kInclusive, c.include().context)); 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, filter_t::kExclusive, relationship_t::kDependency,
c.exclude().dependencies, true)); c.exclude().dependencies, true));
if (c.type() == diagram_t::kInclude) {
std::vector<std::string> dependants;
std::vector<std::string> 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<include_diagram::model::diagram,
common::model::source_file>>(filter_t::kExclusive,
relationship_t::kAssociation, dependencies, true));
add_exclusive_filter(std::make_unique<
tree_element_filter<include_diagram::model::diagram,
common::model::source_file>>(filter_t::kExclusive,
relationship_t::kAssociation, dependants));
}
add_exclusive_filter(std::make_unique<context_filter>( add_exclusive_filter(std::make_unique<context_filter>(
filter_t::kExclusive, c.exclude().context)); filter_t::kExclusive, c.exclude().context));
} }

View File

@@ -25,6 +25,7 @@
#include "config/config.h" #include "config/config.h"
#include "cx/util.h" #include "cx/util.h"
#include "diagram.h" #include "diagram.h"
#include "include_diagram/model/diagram.h"
#include "source_file.h" #include "source_file.h"
#include "tvl.h" #include "tvl.h"
@@ -79,6 +80,9 @@ struct anyof_filter : public filter_visitor {
tvl::value_t match( tvl::value_t match(
const diagram &d, const common::model::element &e) const override; 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: private:
std::vector<std::unique_ptr<filter_visitor>> filters_; std::vector<std::unique_ptr<filter_visitor>> filters_;
}; };
@@ -112,7 +116,8 @@ private:
std::vector<std::string> roots_; std::vector<std::string> roots_;
}; };
template <typename DiagramT, typename ElementT> template <typename DiagramT, typename ElementT,
typename MatchOverrideT = common::model::element>
struct tree_element_filter : public filter_visitor { struct tree_element_filter : public filter_visitor {
tree_element_filter(filter_t type, relationship_t relationship, tree_element_filter(filter_t type, relationship_t relationship,
std::vector<std::string> roots, bool forward = false) std::vector<std::string> 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 // This filter should only be run on the completely generated diagram
// model by visitor // model by visitor
@@ -168,41 +173,92 @@ private:
matching_elements_.emplace(template_ref.value()); matching_elements_.emplace(template_ref.value());
} }
auto match_tree_rel = [&, this](const auto &from, const auto &to) { assert(!matching_elements_.empty());
bool added_new_element{false};
for (const auto &from_el : from) { if constexpr (std::is_same_v<ElementT, common::model::source_file>) {
// Check if any of its relationships of type relationship_ auto match_tree_rel = [&, this](const auto &from, const auto &to) {
// points to an element already in the matching_elements_ bool added_new_element{false};
// set
for (const auto &rel : from_el->relationships()) { for (const auto &from_el : from) {
if (rel.type() == relationship_) { // Check if any of its relationships of type relationship_
for (const auto &to_el : to) { // points to an element already in the matching_elements_
if (rel.destination() == to_el->full_name(false)) { // set
const auto &to_add = forward_ ? to_el : from_el; for (const auto &rel : from_el->relationships()) {
if (matching_elements_.insert(to_add).second) if (rel.type() == relationship_) {
added_new_element = true; 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}; bool keep_looking{true};
while (keep_looking) { while (keep_looking) {
keep_looking = false; keep_looking = false;
if (forward_) { if (forward_) {
if (match_tree_rel( if (match_tree_rel(
matching_elements_, detail::view<ElementT>(cd))) matching_elements_, detail::view<ElementT>(cd)))
keep_looking = true; keep_looking = true;
}
else {
if (match_tree_rel(
detail::view<ElementT>(cd), matching_elements_))
keep_looking = true;
}
} }
else { }
if (match_tree_rel( else {
detail::view<ElementT>(cd), matching_elements_)) auto match_tree_rel = [&, this](const auto &from, const auto &to) {
keep_looking = true; 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<ElementT>(cd)))
keep_looking = true;
}
else {
if (match_tree_rel(
detail::view<ElementT>(cd), matching_elements_))
keep_looking = true;
}
} }
} }

View File

@@ -129,4 +129,15 @@ template <> struct hash<clanguml::common::model::filesystem_path> {
} }
}; };
template <>
struct hash<type_safe::object_ref<const clanguml::common::model::source_file>> {
std::size_t operator()(
const type_safe::object_ref<const clanguml::common::model::source_file>
&key) const
{
using clanguml::common::model::source_file;
return std::hash<std::string>{}(key.get().full_name(false));
}
};
} }

View File

@@ -42,7 +42,11 @@ void generator::generate_relationships(
} }
else { else {
for (const auto &r : f.relationships()) { 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() << " " ostr << f.alias() << " "
<< plantuml_common::to_plantuml(r.type(), r.style()) << " " << plantuml_common::to_plantuml(r.type(), r.style()) << " "
<< r.destination() << '\n'; << r.destination() << '\n';
@@ -63,15 +67,21 @@ void generator::generate(const source_file &f, std::ostream &ostr) const
generate(dynamic_cast<const source_file &>(*file), ostr); generate(dynamic_cast<const source_file &>(*file), ostr);
} }
ostr << "}" << '\n'; ostr << "}" << '\n';
m_generated_aliases.emplace(f.alias());
} }
else { 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) { if (m_config.generate_links) {
generate_link(ostr, f); 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 // Generate files and folders
for (const auto &p : m_model) { for (const auto &p : m_model) {
generate(dynamic_cast<source_file &>(*p), ostr); if (p->type() == common::model::source_file_t::kDirectory ||
m_model.should_include(*p))
generate(dynamic_cast<source_file &>(*p), ostr);
} }
// Process file include relationships // Process file include relationships

View File

@@ -25,7 +25,7 @@ namespace clanguml::include_diagram::model {
common::model::diagram_t diagram::type() const common::model::diagram_t diagram::type() const
{ {
return common::model::diagram_t::kPackage; return common::model::diagram_t::kInclude;
} }
type_safe::optional_ref<const common::model::diagram_element> diagram::get( type_safe::optional_ref<const common::model::diagram_element> diagram::get(
@@ -94,11 +94,18 @@ std::string diagram::to_alias(const std::string &full_name) const
return source_file.value().alias(); return source_file.value().alias();
} }
const std::vector<
type_safe::object_ref<const common::model::source_file, false>> &
diagram::files() const
{
return files_;
}
} }
namespace clanguml::common::model { namespace clanguml::common::model {
template <> template <>
bool check_diagram_type<clanguml::include_diagram::model::diagram>(diagram_t t) bool check_diagram_type<include_diagram::model::diagram>(diagram_t t)
{ {
return t == diagram_t::kInclude; return t == diagram_t::kInclude;
} }

View File

@@ -52,6 +52,10 @@ public:
std::string to_alias(const std::string &full_name) const; std::string to_alias(const std::string &full_name) const;
const std::vector<
type_safe::object_ref<const common::model::source_file, false>> &
files() const;
private: private:
std::vector<type_safe::object_ref<const common::model::source_file, false>> std::vector<type_safe::object_ref<const common::model::source_file, false>>
files_; files_;

View File

@@ -6,8 +6,8 @@ diagrams:
# Provide the files to parse in order to look # Provide the files to parse in order to look
# for #include directives # for #include directives
glob: glob:
- ../../tests/t40003/**/*.cc - ../../tests/t40003/include/**/*.h
- ../../tests/t40003/**/*.h - ../../tests/t40003/src/**/*.cc
# Render the paths relative to this directory # Render the paths relative to this directory
relative_to: ../../tests/t40003 relative_to: ../../tests/t40003
include: include:

View File

@@ -1,5 +1,5 @@
#pragma once #pragma once
namespace clanguml::t40003::dependants { namespace clanguml::t40003::dependants {
void t1() {} void t1() { }
} }

View File

@@ -1,5 +1,5 @@
#pragma once #pragma once
namespace clanguml::t40003::dependencies { namespace clanguml::t40003::dependencies {
void t1() {} void t1() { }
} }

View File

@@ -3,5 +3,5 @@
#include "t1.h" #include "t1.h"
namespace clanguml::t40003::dependencies { namespace clanguml::t40003::dependencies {
void t2() {t1();} void t2() { t1(); }
} }

View File

@@ -3,5 +3,5 @@
#include "t2.h" #include "t2.h"
namespace clanguml::t40003::dependencies { namespace clanguml::t40003::dependencies {
void t3() {t2();} void t3() { t2(); }
} }

View File

@@ -1,5 +1,7 @@
#pragma once #pragma once
#include "t1.h"
namespace clanguml::t40003::dependencies { namespace clanguml::t40003::dependencies {
void t5() { } void t5() { t1(); }
} }

View File

@@ -0,0 +1,7 @@
#pragma once
#include "t1.h"
namespace clanguml::t40003::dependencies {
void t6() { t1(); }
}

View File

@@ -1,48 +1,51 @@
/** /**
* tests/t40003/test_case.cc * tests/t40003/test_case.cc
* *
* Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com> * Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
TEST_CASE("t40003", "[test-case][package]") 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, StartsWith("@startuml"));
REQUIRE_THAT(puml, EndsWith("@enduml\n")); REQUIRE_THAT(puml, EndsWith("@enduml\n"));
REQUIRE_THAT(puml, IsFolder("dependants")); REQUIRE_THAT(puml, IsFolder("dependants"));
REQUIRE_THAT(puml, IsFolder("dependencies")); REQUIRE_THAT(puml, IsFolder("dependencies"));
REQUIRE_THAT(puml, !IsFile("t4.h")); REQUIRE_THAT(puml, IsFile("t1.h"));
REQUIRE_THAT(puml, !IsFile("t5.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(
save_puml( "./" + config.output_directory() + "/" + diagram->name + ".puml", puml);
"./" + config.output_directory() + "/" + diagram->name + ".puml", puml);
} }