Added initial support for include graph diagrams

This commit is contained in:
Bartek Kryza
2022-04-10 13:32:59 +02:00
parent 46e8885c41
commit ac624c9247
23 changed files with 378 additions and 61 deletions

View File

@@ -27,6 +27,8 @@ Main features supported so far include:
* **Package diagram generation** * **Package diagram generation**
* Generation of package diagram based on C++ namespaces * Generation of package diagram based on C++ namespaces
* Interactive links to online code to packages * Interactive links to online code to packages
* **Include graph diagram generation**
* Show include graph for selected files
To see what `clang-uml` can do so far, checkout the diagrams generated for unit test cases [here](./docs/test_cases.md). To see what `clang-uml` can do so far, checkout the diagrams generated for unit test cases [here](./docs/test_cases.md).

View File

@@ -85,4 +85,12 @@ bool diagram::should_include(
return filter_->should_include(ns, name); return filter_->should_include(ns, name);
} }
bool diagram::should_include(const common::model::source_file &f) const
{
if (filter_.get() == nullptr)
return true;
return filter_->should_include(f);
}
} }

View File

@@ -20,6 +20,7 @@
#include "diagram_element.h" #include "diagram_element.h"
#include "enums.h" #include "enums.h"
#include "namespace.h" #include "namespace.h"
#include "source_file.h"
#include <type_safe/optional_ref.hpp> #include <type_safe/optional_ref.hpp>
@@ -58,9 +59,11 @@ public:
// TODO: refactor to a template method // TODO: refactor to a template method
bool should_include(const element &e) const; bool should_include(const element &e) const;
bool should_include(const std::string &e) const; bool should_include(const std::string &e) const;
bool should_include(const source_file &path) const;
bool should_include(const relationship r) const; bool should_include(const relationship r) const;
bool should_include(const relationship_t r) const; bool should_include(const relationship_t r) const;
bool should_include(const access_t s) const; bool should_include(const access_t s) const;
bool should_include(const namespace_ &ns, const std::string &name) const; bool should_include(const namespace_ &ns, const std::string &name) const;
private: private:

View File

@@ -49,6 +49,12 @@ tvl::value_t filter_visitor::match(
return {}; return {};
} }
tvl::value_t filter_visitor::match(
const diagram &d, const common::model::source_file &f) const
{
return {};
}
bool filter_visitor::is_inclusive() const bool filter_visitor::is_inclusive() const
{ {
return type_ == filter_t::kInclusive; return type_ == filter_t::kInclusive;
@@ -254,6 +260,40 @@ tvl::value_t context_filter::match(const diagram &d, const element &e) const
}); });
} }
paths_filter::paths_filter(filter_t type, const std::filesystem::path &root,
std::vector<std::filesystem::path> p)
: filter_visitor{type}
, root_{root}
{
for (auto &&path : p) {
std::filesystem::path absolute_path;
if (path.is_relative())
absolute_path = root / path;
absolute_path = absolute_path.lexically_normal();
paths_.emplace_back(std::move(absolute_path));
}
}
tvl::value_t paths_filter::match(
const diagram &d, const common::model::source_file &p) const
{
if (paths_.empty()) {
return {};
}
auto pp = p.fs_path(root_);
for (const auto &path : paths_) {
if (util::starts_with(pp, path))
return true;
}
return false;
}
diagram_filter::diagram_filter( diagram_filter::diagram_filter(
const common::model::diagram &d, const config::diagram &c) const common::model::diagram &d, const config::diagram &c)
: diagram_{d} : diagram_{d}
@@ -295,6 +335,8 @@ void diagram_filter::init_filters(const config::diagram &c)
filter_t::kInclusive, c.include().relationships)); filter_t::kInclusive, c.include().relationships));
inclusive_.emplace_back(std::make_unique<access_filter>( inclusive_.emplace_back(std::make_unique<access_filter>(
filter_t::kInclusive, c.include().access)); filter_t::kInclusive, c.include().access));
inclusive_.emplace_back(std::make_unique<paths_filter>(
filter_t::kInclusive, c.base_directory(), c.include().paths));
// Include any of these matches even if one them does not match // Include any of these matches even if one them does not match
std::vector<std::unique_ptr<filter_visitor>> element_filters; std::vector<std::unique_ptr<filter_visitor>> element_filters;
@@ -305,6 +347,7 @@ void diagram_filter::init_filters(const config::diagram &c)
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));
inclusive_.emplace_back(std::make_unique<anyof_filter>( inclusive_.emplace_back(std::make_unique<anyof_filter>(
filter_t::kInclusive, std::move(element_filters))); filter_t::kInclusive, std::move(element_filters)));
} }
@@ -323,6 +366,8 @@ void diagram_filter::init_filters(const config::diagram &c)
filter_t::kExclusive, c.exclude().subclasses)); filter_t::kExclusive, c.exclude().subclasses));
exclusive_.emplace_back(std::make_unique<context_filter>( exclusive_.emplace_back(std::make_unique<context_filter>(
filter_t::kExclusive, c.exclude().context)); filter_t::kExclusive, c.exclude().context));
exclusive_.emplace_back(std::make_unique<paths_filter>(
filter_t::kInclusive, c.base_directory(), c.exclude().paths));
} }
} }

View File

@@ -25,8 +25,11 @@
#include "config/config.h" #include "config/config.h"
#include "cx/util.h" #include "cx/util.h"
#include "diagram.h" #include "diagram.h"
#include "source_file.h"
#include "tvl.h" #include "tvl.h"
#include <filesystem>
namespace clanguml::common::model { namespace clanguml::common::model {
enum filter_t { kInclusive, kExclusive }; enum filter_t { kInclusive, kExclusive };
@@ -47,6 +50,9 @@ public:
virtual tvl::value_t match( virtual tvl::value_t match(
const diagram &d, const common::model::namespace_ &ns) const; const diagram &d, const common::model::namespace_ &ns) const;
virtual tvl::value_t match(
const diagram &d, const common::model::source_file &f) const;
bool is_inclusive() const; bool is_inclusive() const;
bool is_exclusive() const; bool is_exclusive() const;
@@ -125,6 +131,18 @@ private:
std::vector<std::string> context_; std::vector<std::string> context_;
}; };
struct paths_filter : public filter_visitor {
paths_filter(filter_t type, const std::filesystem::path &root,
std::vector<std::filesystem::path> p);
tvl::value_t match(const diagram &d,
const common::model::source_file &r) const override;
private:
std::vector<std::filesystem::path> paths_;
std::filesystem::path root_;
};
class diagram_filter { class diagram_filter {
public: public:
diagram_filter(const common::model::diagram &d, const config::diagram &c); diagram_filter(const common::model::diagram &d, const config::diagram &c);
@@ -144,7 +162,9 @@ public:
return false; return false;
auto inc = tvl::all_of(inclusive_.begin(), inclusive_.end(), auto inc = tvl::all_of(inclusive_.begin(), inclusive_.end(),
[this, &e](const auto &in) { return in->match(diagram_, e); }); [this, &e](const auto &in) {
return in->match(diagram_, e);
});
if (tvl::is_undefined(inc) || tvl::is_true(inc)) if (tvl::is_undefined(inc) || tvl::is_true(inc))
return true; return true;

View File

@@ -52,7 +52,8 @@ public:
} }
template <typename V = T> template <typename V = T>
void add_element(const Path &path, std::unique_ptr<V> p) void add_element(
const Path &path, std::unique_ptr<V> p)
{ {
assert(p); assert(p);

View File

@@ -1,5 +1,5 @@
/** /**
* src/include_diagram/model/source_file.cc * src/common/model/source_file.cc
* *
* Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com> * Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com>
* *

View File

@@ -1,5 +1,5 @@
/** /**
* src/include_diagram/model/source_file.h * src/common/model/source_file.h
* *
* Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com> * Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com>
* *
@@ -30,9 +30,9 @@
#include <string> #include <string>
#include <vector> #include <vector>
namespace clanguml::include_diagram::model { namespace clanguml::common::model {
enum class source_file_type { kDirectory, kHeader, kImplementation }; enum class source_file_t { kDirectory, kHeader, kImplementation };
struct fs_path_sep { struct fs_path_sep {
static constexpr std::string_view value = "/"; static constexpr std::string_view value = "/";
@@ -43,13 +43,19 @@ using filesystem_path = common::model::path<fs_path_sep>;
class source_file class source_file
: public common::model::diagram_element, : public common::model::diagram_element,
public common::model::stylable_element, public common::model::stylable_element,
public common::model::nested_trait<common::model::diagram_element, public common::model::nested_trait<common::model::source_file,
filesystem_path> { filesystem_path> {
public: public:
source_file() = default; source_file() = default;
void set_path(const filesystem_path &p) { path_ = p; } void set_path(const filesystem_path &p) { path_ = p; }
void set_absolute() { is_absolute_ = true; }
void set_type(source_file_t type) { type_ = type; }
source_file_t type() const { return type_; }
source_file(const source_file &) = delete; source_file(const source_file &) = delete;
source_file(source_file &&) = default; source_file(source_file &&) = default;
source_file &operator=(const source_file &) = delete; source_file &operator=(const source_file &) = delete;
@@ -62,23 +68,41 @@ public:
return (path_ | name()).to_string(); return (path_ | name()).to_string();
} }
void add_file(std::unique_ptr<include_diagram::model::source_file> &&f) void add_file(std::unique_ptr<source_file> &&f)
{ {
LOG_DBG("Adding source file: {}, {}", f->name(), f->full_name(true)); LOG_DBG("Adding source file: {}, {}", f->name(), f->full_name(true));
add_element(f->path(), std::move(f)); add_element(f->path(), std::move(f));
} }
std::filesystem::path fs_path(const std::filesystem::path &base = {}) const
{
std::filesystem::path res;
for (const auto &pe : path_) {
res /= pe;
}
if (is_absolute_)
res = "/" / res;
else
res = base / res;
return res.lexically_normal();
}
private: private:
filesystem_path path_; filesystem_path path_;
source_file_t type_{source_file_t::kDirectory};
bool is_absolute_{false};
}; };
} }
namespace std { namespace std {
template <> struct hash<clanguml::include_diagram::model::filesystem_path> { template <> struct hash<clanguml::common::model::filesystem_path> {
std::size_t operator()( std::size_t operator()(
const clanguml::include_diagram::model::filesystem_path &key) const const clanguml::common::model::filesystem_path &key) const
{ {
using clanguml::common::model::path; using clanguml::common::model::path;

View File

@@ -15,7 +15,9 @@
* 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.
*/ */
#include "config.h" #include "config.h"
#include <filesystem> #include <filesystem>
namespace clanguml { namespace clanguml {
@@ -28,12 +30,13 @@ config load(const std::string &config_file)
// Store the parent path of the config_file to properly resolve // Store the parent path of the config_file to properly resolve
// the include files paths // the include files paths
auto config_file_path = std::filesystem::path{config_file}; auto config_file_path =
std::filesystem::absolute(std::filesystem::path{config_file});
doc.force_insert( doc.force_insert(
"__parent_path", config_file_path.parent_path().string()); "__parent_path", config_file_path.parent_path().string());
// If the current directory is also a git repository, // If the current directory is also a git repository,
// load some config values which can be included in the // load some config values, which can be included in the
// generated diagrams // generated diagrams
if (util::is_git_repository() && !doc["git"]) { if (util::is_git_repository() && !doc["git"]) {
YAML::Node git_config{YAML::NodeType::Map}; YAML::Node git_config{YAML::NodeType::Map};
@@ -108,6 +111,8 @@ void inheritable_diagram_options::inherit(
generate_method_arguments.override(parent.generate_method_arguments); generate_method_arguments.override(parent.generate_method_arguments);
generate_links.override(parent.generate_links); generate_links.override(parent.generate_links);
git.override(parent.git); git.override(parent.git);
base_directory.override(parent.base_directory);
relative_to.override(parent.relative_to);
} }
diagram_type class_diagram::type() const { return diagram_type::class_diagram; } diagram_type class_diagram::type() const { return diagram_type::class_diagram; }
@@ -245,6 +250,21 @@ std::shared_ptr<clanguml::config::diagram> parse_diagram_config(const Node &d)
return {}; return {};
} }
//
// config std::filesystem::path decoder
//
template <> struct convert<std::filesystem::path> {
static bool decode(const Node &node, std::filesystem::path &rhs)
{
if (!node.IsScalar())
return false;
rhs = std::filesystem::path{node.as<std::string>()};
return true;
}
};
// //
// config access_t decoder // config access_t decoder
// //
@@ -391,6 +411,9 @@ template <> struct convert<filter> {
if (node["context"]) if (node["context"])
rhs.context = node["context"].as<decltype(rhs.context)>(); rhs.context = node["context"].as<decltype(rhs.context)>();
if (node["paths"])
rhs.paths = node["paths"].as<decltype(rhs.paths)>();
return true; return true;
} }
}; };
@@ -505,6 +528,16 @@ template <> struct convert<include_diagram> {
return false; return false;
get_option(node, rhs.layout); get_option(node, rhs.layout);
get_option(node, rhs.relative_to);
// Convert the path in relative_to to an absolute path, with respect
// to the directory where the `.clang-uml` configuration file is located
if (rhs.relative_to) {
auto absolute_relative_to =
std::filesystem::path{node["__parent_path"].as<std::string>()} /
rhs.relative_to();
rhs.relative_to.set(absolute_relative_to.lexically_normal());
}
return true; return true;
} }
@@ -558,25 +591,29 @@ template <> struct convert<config> {
get_option(node, rhs.generate_packages); get_option(node, rhs.generate_packages);
get_option(node, rhs.generate_links); get_option(node, rhs.generate_links);
get_option(node, rhs.git); get_option(node, rhs.git);
rhs.base_directory.set(node["__parent_path"].as<std::string>());
get_option(node, rhs.relative_to);
auto diagrams = node["diagrams"]; auto diagrams = node["diagrams"];
assert(diagrams.Type() == NodeType::Map); assert(diagrams.Type() == NodeType::Map);
for (const auto &d : diagrams) { for (auto d : diagrams) {
auto name = d.first.as<std::string>(); auto name = d.first.as<std::string>();
std::shared_ptr<clanguml::config::diagram> diagram_config{}; std::shared_ptr<clanguml::config::diagram> diagram_config{};
auto parent_path = node["__parent_path"].as<std::string>();
if (has_key(d.second, "include!")) { if (has_key(d.second, "include!")) {
auto parent_path = node["__parent_path"].as<std::string>();
auto include_path = std::filesystem::path{parent_path}; auto include_path = std::filesystem::path{parent_path};
include_path /= d.second["include!"].as<std::string>(); include_path /= d.second["include!"].as<std::string>();
YAML::Node node = YAML::LoadFile(include_path.string()); YAML::Node node = YAML::LoadFile(include_path.string());
node.force_insert("__parent_path", parent_path);
diagram_config = parse_diagram_config(node); diagram_config = parse_diagram_config(node);
} }
else { else {
d.second.force_insert("__parent_path", parent_path);
diagram_config = parse_diagram_config(d.second); diagram_config = parse_diagram_config(d.second);
} }
@@ -585,6 +622,9 @@ template <> struct convert<config> {
diagram_config->inherit(rhs); diagram_config->inherit(rhs);
rhs.diagrams[name] = diagram_config; rhs.diagrams[name] = diagram_config;
} }
else {
return false;
}
} }
return true; return true;

View File

@@ -71,6 +71,8 @@ struct filter {
std::vector<std::string> subclasses; std::vector<std::string> subclasses;
std::vector<std::string> context; std::vector<std::string> context;
std::vector<std::filesystem::path> paths;
}; };
enum class hint_t { up, down, left, right }; enum class hint_t { up, down, left, right };
@@ -110,6 +112,8 @@ struct inheritable_diagram_options {
option<bool> generate_packages{"generate_packages", false}; option<bool> generate_packages{"generate_packages", false};
option<generate_links_config> generate_links{"generate_links"}; option<generate_links_config> generate_links{"generate_links"};
option<git_config> git{"git"}; option<git_config> git{"git"};
option<std::filesystem::path> base_directory{"__parent_path"};
option<std::filesystem::path> relative_to{"relative_to"};
void inherit(const inheritable_diagram_options &parent); void inherit(const inheritable_diagram_options &parent);
}; };

View File

@@ -31,11 +31,41 @@ void generator::generate_relationships(
const source_file &f, std::ostream &ostr) const const source_file &f, std::ostream &ostr) const
{ {
LOG_DBG("Generating relationships for file {}", f.full_name(true)); LOG_DBG("Generating relationships for file {}", f.full_name(true));
namespace plantuml_common = clanguml::common::generators::plantuml;
if (f.type() == common::model::source_file_t::kDirectory) {
for (const auto &file : f) {
generate_relationships(
dynamic_cast<const source_file &>(*file), ostr);
}
}
else {
for (const auto &r : f.relationships()) {
if (m_model.should_include(r.type())) {
ostr << f.alias() << " "
<< plantuml_common::to_plantuml(r.type(), r.style()) << " "
<< r.destination() << '\n';
}
}
}
} }
void generator::generate(const source_file &f, std::ostream &ostr) const void generator::generate(const source_file &f, std::ostream &ostr) const
{ {
LOG_DBG("Generating source_file {}", f.name()); LOG_DBG("Generating source_file {}", f.name());
if (f.type() == common::model::source_file_t::kDirectory) {
ostr << "folder " << f.name();
ostr << " as " << f.alias() << " {\n";
for (const auto &file : f) {
generate(dynamic_cast<const source_file &>(*file), ostr);
}
ostr << "}" << '\n';
}
else {
ostr << "file " << f.name() << " as " << f.alias() << '\n';
}
} }
void generator::generate(std::ostream &ostr) const void generator::generate(std::ostream &ostr) const
@@ -44,15 +74,14 @@ void generator::generate(std::ostream &ostr) const
generate_plantuml_directives(ostr, m_config.puml().before); generate_plantuml_directives(ostr, m_config.puml().before);
// Generate files and folders
for (const auto &p : m_model) { for (const auto &p : m_model) {
generate(dynamic_cast<source_file &>(*p), ostr); generate(dynamic_cast<source_file &>(*p), ostr);
ostr << '\n';
} }
// Process package relationships // Process file include relationships
for (const auto &p : m_model) { for (const auto &p : m_model) {
generate_relationships(dynamic_cast<source_file &>(*p), ostr); generate_relationships(dynamic_cast<source_file &>(*p), ostr);
ostr << '\n';
} }
generate_config_layout_hints(ostr); generate_config_layout_hints(ostr);

View File

@@ -20,10 +20,10 @@
#include "common/generators/plantuml/generator.h" #include "common/generators/plantuml/generator.h"
#include "common/model/package.h" #include "common/model/package.h"
#include "common/model/relationship.h" #include "common/model/relationship.h"
#include "common/model/source_file.h"
#include "config/config.h" #include "config/config.h"
#include "cx/compilation_database.h" #include "cx/compilation_database.h"
#include "include_diagram/model/diagram.h" #include "include_diagram/model/diagram.h"
#include "include_diagram/model/source_file.h"
#include "include_diagram/visitor/translation_unit_visitor.h" #include "include_diagram/visitor/translation_unit_visitor.h"
#include "util/util.h" #include "util/util.h"
@@ -50,7 +50,7 @@ using common_generator =
using clanguml::common::model::access_t; using clanguml::common::model::access_t;
using clanguml::common::model::package; using clanguml::common::model::package;
using clanguml::common::model::relationship_t; using clanguml::common::model::relationship_t;
using clanguml::include_diagram::model::source_file; using clanguml::common::model::source_file;
using namespace clanguml::util; using namespace clanguml::util;
class generator : public common_generator<diagram_config, diagram_model> { class generator : public common_generator<diagram_config, diagram_model> {

View File

@@ -34,16 +34,18 @@ type_safe::optional_ref<const common::model::diagram_element> diagram::get(
return get_file(full_name); return get_file(full_name);
} }
void diagram::add_file(std::unique_ptr<include_diagram::model::source_file> &&f) void diagram::add_file(std::unique_ptr<common::model::source_file> &&f)
{ {
LOG_DBG("Adding source file: {}, {}", f->name(), f->full_name(true)); LOG_DBG("Adding source file: {}, {}", f->name(), f->full_name(true));
files_.emplace_back(*f); files_.emplace_back(*f);
add_element(f->path(), std::move(f)); auto p = f->path();
add_element(p, std::move(f));
} }
type_safe::optional_ref<const include_diagram::model::source_file> type_safe::optional_ref<const common::model::source_file>
diagram::get_file(const std::string &name) const diagram::get_file(const std::string &name) const
{ {
for (const auto &p : files_) { for (const auto &p : files_) {

View File

@@ -19,7 +19,7 @@
#include "common/model/diagram.h" #include "common/model/diagram.h"
#include "common/model/package.h" #include "common/model/package.h"
#include "source_file.h" #include "common/model/source_file.h"
#include <type_safe/optional_ref.hpp> #include <type_safe/optional_ref.hpp>
@@ -29,8 +29,9 @@
namespace clanguml::include_diagram::model { namespace clanguml::include_diagram::model {
class diagram : public clanguml::common::model::diagram, class diagram : public clanguml::common::model::diagram,
public clanguml::common::model::nested_trait<source_file, public clanguml::common::model::nested_trait<
filesystem_path> { clanguml::common::model::source_file,
clanguml::common::model::filesystem_path> {
public: public:
diagram() = default; diagram() = default;
@@ -44,16 +45,15 @@ public:
type_safe::optional_ref<const common::model::diagram_element> get( type_safe::optional_ref<const common::model::diagram_element> get(
const std::string &full_name) const; const std::string &full_name) const;
void add_file(std::unique_ptr<include_diagram::model::source_file> &&f); void add_file(std::unique_ptr<common::model::source_file> &&f);
type_safe::optional_ref<const include_diagram::model::source_file> get_file( type_safe::optional_ref<const common::model::source_file> get_file(
const std::string &name) const; const std::string &name) const;
std::string to_alias(const std::string &full_name) const; std::string to_alias(const std::string &full_name) const;
private: private:
std::vector< std::vector<type_safe::object_ref<const common::model::source_file, false>>
type_safe::object_ref<const include_diagram::model::source_file, false>>
files_; files_;
}; };
} }

View File

@@ -49,12 +49,12 @@ clanguml::include_diagram::model::diagram &translation_unit_context::diagram()
} }
void translation_unit_context::set_current_file( void translation_unit_context::set_current_file(
type_safe::optional_ref<include_diagram::model::source_file> f) type_safe::optional_ref<common::model::source_file> f)
{ {
current_file_ = f; current_file_ = f;
} }
type_safe::optional_ref<include_diagram::model::source_file> type_safe::optional_ref<common::model::source_file>
translation_unit_context::get_current_file() const translation_unit_context::get_current_file() const
{ {
return current_file_; return current_file_;

View File

@@ -41,9 +41,9 @@ public:
clanguml::include_diagram::model::diagram &diagram(); clanguml::include_diagram::model::diagram &diagram();
void set_current_file( void set_current_file(
type_safe::optional_ref<include_diagram::model::source_file> p); type_safe::optional_ref<common::model::source_file> p);
type_safe::optional_ref<include_diagram::model::source_file> type_safe::optional_ref<common::model::source_file>
get_current_file() const; get_current_file() const;
private: private:
@@ -56,7 +56,7 @@ private:
// Reference to class diagram config // Reference to class diagram config
const clanguml::config::include_diagram &config_; const clanguml::config::include_diagram &config_;
type_safe::optional_ref<include_diagram::model::source_file> current_file_; type_safe::optional_ref<common::model::source_file> current_file_;
}; };
} }

View File

@@ -21,9 +21,9 @@
#include <cppast/cpp_entity_kind.hpp> #include <cppast/cpp_entity_kind.hpp>
#include <cppast/cpp_preprocessor.hpp> #include <cppast/cpp_preprocessor.hpp>
namespace clanguml::include_diagram::visitor { #include <filesystem>
using clanguml::include_diagram::model::diagram; namespace clanguml::include_diagram::visitor {
translation_unit_visitor::translation_unit_visitor( translation_unit_visitor::translation_unit_visitor(
cppast::cpp_entity_index &idx, cppast::cpp_entity_index &idx,
@@ -35,19 +35,116 @@ translation_unit_visitor::translation_unit_visitor(
void translation_unit_visitor::operator()(const cppast::cpp_entity &file) void translation_unit_visitor::operator()(const cppast::cpp_entity &file)
{ {
assert(file.kind() == cppast::cpp_entity_kind::file_t);
const auto &f = static_cast<const cppast::cpp_file &>(file);
auto file_path = f.name();
LOG_DBG("Processing source file {}", file_path);
process_file(file_path, true);
cppast::visit(file, cppast::visit(file,
[&, this](const cppast::cpp_entity &e, cppast::visitor_info info) { [&, this](const cppast::cpp_entity &e, cppast::visitor_info info) {
if (e.kind() == cppast::cpp_entity_kind::include_directive_t) { if (e.kind() == cppast::cpp_entity_kind::include_directive_t) {
assert(ctx.get_current_file().has_value());
auto file_path_cpy = file.name();
const auto &inc = const auto &inc =
static_cast<const cppast::cpp_include_directive &>(e); static_cast<const cppast::cpp_include_directive &>(e);
auto file_name = std::filesystem::path(inc.full_path()); LOG_DBG("Processing include directive {} in file {}",
inc.full_path(), ctx.get_current_file().value().name());
auto f = std::make_unique<model::source_file>(); process_file(inc.full_path(), false, inc.include_kind());
f->set_path(file_name.parent_path().string());
f->set_name(file_name.filename().string());
} }
}); });
} }
void translation_unit_visitor::process_file(const std::string &file,
bool register_as_current,
std::optional<cppast::cpp_include_kind> include_kind)
{
auto include_path = std::filesystem::path(file);
const std::filesystem::path base_directory{ctx.config().base_directory()};
if (include_path.is_relative()) {
include_path = ctx.config().base_directory() / include_path;
}
include_path = include_path.lexically_normal();
auto f_abs = std::make_unique<common::model::source_file>();
if (include_path.is_absolute())
f_abs->set_absolute();
f_abs->set_path(include_path.parent_path().string());
f_abs->set_name(include_path.filename().string());
if (ctx.diagram().should_include(*f_abs)) {
if (ctx.config().relative_to) {
const std::filesystem::path relative_to{ctx.config().relative_to()};
include_path = std::filesystem::relative(include_path, relative_to);
}
auto f = std::make_unique<common::model::source_file>();
auto parent_path_str = include_path.parent_path().string();
if (!parent_path_str.empty())
f->set_path(parent_path_str);
f->set_name(include_path.filename().string());
f->set_type(common::model::source_file_t::kHeader);
if (register_as_current &&
ctx.diagram().get_element(f->path() | f->name()).has_value()) {
// This file is already in the diagram (e.g. it was added through an
// include directive before it was visited by the parser)
ctx.set_current_file(
ctx.diagram().get_element(f->path() | f->name()));
return;
}
if (!f->path().is_empty()) {
// Ensure parent path directories source_files
// are in the diagram
common::model::filesystem_path parent_path_so_far;
for (const auto &directory : f->path()) {
auto dir = std::make_unique<common::model::source_file>();
if (!parent_path_so_far.is_empty())
dir->set_path(parent_path_so_far);
dir->set_name(directory);
dir->set_type(common::model::source_file_t::kDirectory);
if (!ctx.diagram()
.get_element(parent_path_so_far | directory)
.has_value())
ctx.diagram().add_file(std::move(dir));
parent_path_so_far.append(directory);
}
}
if (!register_as_current) {
auto relationship_type =
common::model::relationship_t::kAssociation;
if (include_kind.has_value() &&
include_kind.value() == cppast::cpp_include_kind::system)
relationship_type = common::model::relationship_t::kDependency;
ctx.get_current_file().value().add_relationship(
common::model::relationship{relationship_type, f->alias()});
}
if (!ctx.diagram().get_element(f->path() | f->name()).has_value()) {
ctx.set_current_file(type_safe::opt_ref(*f));
ctx.diagram().add_file(std::move(f));
}
}
}
} }

View File

@@ -21,14 +21,15 @@
#include "cx/cursor.h" #include "cx/cursor.h"
#include "include_diagram/model/diagram.h" #include "include_diagram/model/diagram.h"
#include "include_diagram/visitor/translation_unit_context.h" #include "include_diagram/visitor/translation_unit_context.h"
#include "common/model/enums.h"
#include "common/model/package.h"
#include <clang-c/CXCompilationDatabase.h> #include <clang-c/CXCompilationDatabase.h>
#include <clang-c/Index.h> #include <clang-c/Index.h>
#include <cppast/visitor.hpp> #include <cppast/visitor.hpp>
#include <cppast/cpp_preprocessor.hpp>
#include <type_safe/reference.hpp> #include <type_safe/reference.hpp>
#include <common/model/enums.h>
#include <common/model/package.h>
#include <functional> #include <functional>
#include <map> #include <map>
#include <memory> #include <memory>
@@ -45,6 +46,9 @@ public:
void operator()(const cppast::cpp_entity &file); void operator()(const cppast::cpp_entity &file);
private: private:
void process_file(const std::string &file, bool register_as_current = false,
std::optional<cppast::cpp_include_kind> include_kind = {});
// ctx allows to track current visitor context, e.g. current namespace // ctx allows to track current visitor context, e.g. current namespace
translation_unit_context ctx; translation_unit_context ctx;
}; };

View File

@@ -198,5 +198,37 @@ bool replace_all(
return replaced; return replaced;
} }
template <>
bool starts_with(
const std::filesystem::path &path, const std::filesystem::path &prefix)
{
if(path == prefix)
return true;
const int path_length = std::distance(std::begin(path), std::end(path));
auto last_nonempty_prefix_element = std::prev(std::find_if(
prefix.begin(), prefix.end(), [](auto &&n) { return n.empty(); }));
int prefix_length =
std::distance(std::begin(prefix), last_nonempty_prefix_element);
// Empty prefix always matches
if (prefix_length == 0)
return true;
// Prefix longer then path never matches
if (prefix_length >= path_length)
return false;
auto path_compare_end = path.begin();
std::advance(path_compare_end, prefix_length);
std::vector<std::string> pref(prefix.begin(), last_nonempty_prefix_element);
std::vector<std::string> pat(path.begin(), path_compare_end);
return pref == pat;
}
} }
} }

View File

@@ -21,6 +21,7 @@
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <algorithm> #include <algorithm>
#include <filesystem>
#include <string.h> #include <string.h>
#include <string> #include <string>
#include <type_traits> #include <type_traits>
@@ -143,23 +144,26 @@ template <typename T> void append(std::vector<T> &l, const std::vector<T> &r)
} }
/** /**
* @brief Checks if vector starts with a prefix. * @brief Checks if container starts with a prefix.
* *
* @tparam T * @tparam T e.g. std::vector<std::string>
* @param col * @param con Container to be checked against prefix
* @param prefix * @param prefix Container, which specifies the prefix
* @return * @return true if first prefix.size() elements of con are equal to prefix
*/ */
template <typename T> template <typename T> bool starts_with(const T &con, const T &prefix)
bool starts_with(const std::vector<T> &col, const std::vector<T> &prefix)
{ {
if (prefix.size() > col.size()) if (prefix.size() > con.size())
return false; return false;
return std::vector<std::string>(prefix.begin(), prefix.end()) == return T(prefix.begin(), prefix.end()) ==
std::vector<std::string>(col.begin(), col.begin() + prefix.size()); T(con.begin(), con.begin() + prefix.size());
} }
template <>
bool starts_with(
const std::filesystem::path &path, const std::filesystem::path &prefix);
template <typename T> template <typename T>
bool ends_with(const std::vector<T> &col, const std::vector<T> &suffix) bool ends_with(const std::vector<T> &col, const std::vector<T> &suffix)
{ {

View File

@@ -0,0 +1,9 @@
#pragma once
#include "lib1/lib1.h"
namespace clanguml::t40001 {
int foo() { return lib1::foo2(); }
}

View File

@@ -1,7 +1,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "t40001_include1.h" #include "include/t40001_include1.h"
namespace clanguml { namespace clanguml {
namespace t40001 { namespace t40001 {

View File

@@ -1,7 +0,0 @@
#pragma once
namespace clanguml::t40001 {
int foo() { return 0; }
}