Added initial support for include graph diagrams
This commit is contained in:
@@ -85,4 +85,12 @@ bool diagram::should_include(
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "diagram_element.h"
|
||||
#include "enums.h"
|
||||
#include "namespace.h"
|
||||
#include "source_file.h"
|
||||
|
||||
#include <type_safe/optional_ref.hpp>
|
||||
|
||||
@@ -58,9 +59,11 @@ public:
|
||||
// TODO: refactor to a template method
|
||||
bool should_include(const element &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_t r) const;
|
||||
bool should_include(const access_t s) const;
|
||||
|
||||
bool should_include(const namespace_ &ns, const std::string &name) const;
|
||||
|
||||
private:
|
||||
|
||||
@@ -49,6 +49,12 @@ tvl::value_t filter_visitor::match(
|
||||
return {};
|
||||
}
|
||||
|
||||
tvl::value_t filter_visitor::match(
|
||||
const diagram &d, const common::model::source_file &f) const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
bool filter_visitor::is_inclusive() const
|
||||
{
|
||||
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(
|
||||
const common::model::diagram &d, const config::diagram &c)
|
||||
: diagram_{d}
|
||||
@@ -295,6 +335,8 @@ void diagram_filter::init_filters(const config::diagram &c)
|
||||
filter_t::kInclusive, c.include().relationships));
|
||||
inclusive_.emplace_back(std::make_unique<access_filter>(
|
||||
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
|
||||
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>(
|
||||
filter_t::kInclusive, c.include().context));
|
||||
|
||||
|
||||
inclusive_.emplace_back(std::make_unique<anyof_filter>(
|
||||
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));
|
||||
exclusive_.emplace_back(std::make_unique<context_filter>(
|
||||
filter_t::kExclusive, c.exclude().context));
|
||||
exclusive_.emplace_back(std::make_unique<paths_filter>(
|
||||
filter_t::kInclusive, c.base_directory(), c.exclude().paths));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,8 +25,11 @@
|
||||
#include "config/config.h"
|
||||
#include "cx/util.h"
|
||||
#include "diagram.h"
|
||||
#include "source_file.h"
|
||||
#include "tvl.h"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace clanguml::common::model {
|
||||
|
||||
enum filter_t { kInclusive, kExclusive };
|
||||
@@ -47,6 +50,9 @@ public:
|
||||
virtual tvl::value_t match(
|
||||
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_exclusive() const;
|
||||
|
||||
@@ -125,6 +131,18 @@ private:
|
||||
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 {
|
||||
public:
|
||||
diagram_filter(const common::model::diagram &d, const config::diagram &c);
|
||||
@@ -144,7 +162,9 @@ public:
|
||||
return false;
|
||||
|
||||
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))
|
||||
return true;
|
||||
|
||||
@@ -52,7 +52,8 @@ public:
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
||||
@@ -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>
|
||||
*
|
||||
@@ -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>
|
||||
*
|
||||
@@ -30,9 +30,9 @@
|
||||
#include <string>
|
||||
#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 {
|
||||
static constexpr std::string_view value = "/";
|
||||
@@ -43,13 +43,19 @@ using filesystem_path = common::model::path<fs_path_sep>;
|
||||
class source_file
|
||||
: public common::model::diagram_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> {
|
||||
public:
|
||||
source_file() = default;
|
||||
|
||||
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(source_file &&) = default;
|
||||
source_file &operator=(const source_file &) = delete;
|
||||
@@ -62,23 +68,41 @@ public:
|
||||
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));
|
||||
|
||||
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:
|
||||
filesystem_path path_;
|
||||
source_file_t type_{source_file_t::kDirectory};
|
||||
bool is_absolute_{false};
|
||||
};
|
||||
}
|
||||
|
||||
namespace std {
|
||||
|
||||
template <> struct hash<clanguml::include_diagram::model::filesystem_path> {
|
||||
template <> struct hash<clanguml::common::model::filesystem_path> {
|
||||
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;
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace clanguml {
|
||||
@@ -28,12 +30,13 @@ config load(const std::string &config_file)
|
||||
|
||||
// Store the parent path of the config_file to properly resolve
|
||||
// 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(
|
||||
"__parent_path", config_file_path.parent_path().string());
|
||||
|
||||
// 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
|
||||
if (util::is_git_repository() && !doc["git"]) {
|
||||
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_links.override(parent.generate_links);
|
||||
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; }
|
||||
@@ -245,6 +250,21 @@ std::shared_ptr<clanguml::config::diagram> parse_diagram_config(const Node &d)
|
||||
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
|
||||
//
|
||||
@@ -391,6 +411,9 @@ template <> struct convert<filter> {
|
||||
if (node["context"])
|
||||
rhs.context = node["context"].as<decltype(rhs.context)>();
|
||||
|
||||
if (node["paths"])
|
||||
rhs.paths = node["paths"].as<decltype(rhs.paths)>();
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
@@ -505,6 +528,16 @@ template <> struct convert<include_diagram> {
|
||||
return false;
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -558,25 +591,29 @@ template <> struct convert<config> {
|
||||
get_option(node, rhs.generate_packages);
|
||||
get_option(node, rhs.generate_links);
|
||||
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"];
|
||||
|
||||
assert(diagrams.Type() == NodeType::Map);
|
||||
|
||||
for (const auto &d : diagrams) {
|
||||
for (auto d : diagrams) {
|
||||
auto name = d.first.as<std::string>();
|
||||
std::shared_ptr<clanguml::config::diagram> diagram_config{};
|
||||
auto parent_path = node["__parent_path"].as<std::string>();
|
||||
|
||||
if (has_key(d.second, "include!")) {
|
||||
auto parent_path = node["__parent_path"].as<std::string>();
|
||||
auto include_path = std::filesystem::path{parent_path};
|
||||
include_path /= d.second["include!"].as<std::string>();
|
||||
|
||||
YAML::Node node = YAML::LoadFile(include_path.string());
|
||||
node.force_insert("__parent_path", parent_path);
|
||||
|
||||
diagram_config = parse_diagram_config(node);
|
||||
}
|
||||
else {
|
||||
d.second.force_insert("__parent_path", parent_path);
|
||||
diagram_config = parse_diagram_config(d.second);
|
||||
}
|
||||
|
||||
@@ -585,6 +622,9 @@ template <> struct convert<config> {
|
||||
diagram_config->inherit(rhs);
|
||||
rhs.diagrams[name] = diagram_config;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -71,6 +71,8 @@ struct filter {
|
||||
std::vector<std::string> subclasses;
|
||||
|
||||
std::vector<std::string> context;
|
||||
|
||||
std::vector<std::filesystem::path> paths;
|
||||
};
|
||||
|
||||
enum class hint_t { up, down, left, right };
|
||||
@@ -110,6 +112,8 @@ struct inheritable_diagram_options {
|
||||
option<bool> generate_packages{"generate_packages", false};
|
||||
option<generate_links_config> generate_links{"generate_links"};
|
||||
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);
|
||||
};
|
||||
|
||||
@@ -31,11 +31,41 @@ void generator::generate_relationships(
|
||||
const source_file &f, std::ostream &ostr) const
|
||||
{
|
||||
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
|
||||
{
|
||||
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
|
||||
@@ -44,15 +74,14 @@ void generator::generate(std::ostream &ostr) const
|
||||
|
||||
generate_plantuml_directives(ostr, m_config.puml().before);
|
||||
|
||||
// Generate files and folders
|
||||
for (const auto &p : m_model) {
|
||||
generate(dynamic_cast<source_file &>(*p), ostr);
|
||||
ostr << '\n';
|
||||
}
|
||||
|
||||
// Process package relationships
|
||||
// Process file include relationships
|
||||
for (const auto &p : m_model) {
|
||||
generate_relationships(dynamic_cast<source_file &>(*p), ostr);
|
||||
ostr << '\n';
|
||||
}
|
||||
|
||||
generate_config_layout_hints(ostr);
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
#include "common/generators/plantuml/generator.h"
|
||||
#include "common/model/package.h"
|
||||
#include "common/model/relationship.h"
|
||||
#include "common/model/source_file.h"
|
||||
#include "config/config.h"
|
||||
#include "cx/compilation_database.h"
|
||||
#include "include_diagram/model/diagram.h"
|
||||
#include "include_diagram/model/source_file.h"
|
||||
#include "include_diagram/visitor/translation_unit_visitor.h"
|
||||
#include "util/util.h"
|
||||
|
||||
@@ -50,7 +50,7 @@ using common_generator =
|
||||
using clanguml::common::model::access_t;
|
||||
using clanguml::common::model::package;
|
||||
using clanguml::common::model::relationship_t;
|
||||
using clanguml::include_diagram::model::source_file;
|
||||
using clanguml::common::model::source_file;
|
||||
using namespace clanguml::util;
|
||||
|
||||
class generator : public common_generator<diagram_config, diagram_model> {
|
||||
|
||||
@@ -34,16 +34,18 @@ type_safe::optional_ref<const common::model::diagram_element> diagram::get(
|
||||
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));
|
||||
|
||||
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
|
||||
{
|
||||
for (const auto &p : files_) {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
#include "common/model/diagram.h"
|
||||
#include "common/model/package.h"
|
||||
#include "source_file.h"
|
||||
#include "common/model/source_file.h"
|
||||
|
||||
#include <type_safe/optional_ref.hpp>
|
||||
|
||||
@@ -29,8 +29,9 @@
|
||||
namespace clanguml::include_diagram::model {
|
||||
|
||||
class diagram : public clanguml::common::model::diagram,
|
||||
public clanguml::common::model::nested_trait<source_file,
|
||||
filesystem_path> {
|
||||
public clanguml::common::model::nested_trait<
|
||||
clanguml::common::model::source_file,
|
||||
clanguml::common::model::filesystem_path> {
|
||||
public:
|
||||
diagram() = default;
|
||||
|
||||
@@ -44,16 +45,15 @@ public:
|
||||
type_safe::optional_ref<const common::model::diagram_element> get(
|
||||
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;
|
||||
|
||||
std::string to_alias(const std::string &full_name) const;
|
||||
|
||||
private:
|
||||
std::vector<
|
||||
type_safe::object_ref<const include_diagram::model::source_file, false>>
|
||||
std::vector<type_safe::object_ref<const common::model::source_file, false>>
|
||||
files_;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -49,12 +49,12 @@ clanguml::include_diagram::model::diagram &translation_unit_context::diagram()
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
type_safe::optional_ref<include_diagram::model::source_file>
|
||||
type_safe::optional_ref<common::model::source_file>
|
||||
translation_unit_context::get_current_file() const
|
||||
{
|
||||
return current_file_;
|
||||
|
||||
@@ -41,9 +41,9 @@ public:
|
||||
clanguml::include_diagram::model::diagram &diagram();
|
||||
|
||||
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;
|
||||
|
||||
private:
|
||||
@@ -56,7 +56,7 @@ private:
|
||||
// Reference to class 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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
#include <cppast/cpp_entity_kind.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(
|
||||
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)
|
||||
{
|
||||
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,
|
||||
[&, this](const cppast::cpp_entity &e, cppast::visitor_info info) {
|
||||
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 =
|
||||
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>();
|
||||
f->set_path(file_name.parent_path().string());
|
||||
f->set_name(file_name.filename().string());
|
||||
process_file(inc.full_path(), false, inc.include_kind());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,14 +21,15 @@
|
||||
#include "cx/cursor.h"
|
||||
#include "include_diagram/model/diagram.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/Index.h>
|
||||
#include <cppast/visitor.hpp>
|
||||
#include <cppast/cpp_preprocessor.hpp>
|
||||
#include <type_safe/reference.hpp>
|
||||
|
||||
#include <common/model/enums.h>
|
||||
#include <common/model/package.h>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
@@ -45,6 +46,9 @@ public:
|
||||
void operator()(const cppast::cpp_entity &file);
|
||||
|
||||
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
|
||||
translation_unit_context ctx;
|
||||
};
|
||||
|
||||
@@ -198,5 +198,37 @@ bool replace_all(
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#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
|
||||
* @param col
|
||||
* @param prefix
|
||||
* @return
|
||||
* @tparam T e.g. std::vector<std::string>
|
||||
* @param con Container to be checked against prefix
|
||||
* @param prefix Container, which specifies the prefix
|
||||
* @return true if first prefix.size() elements of con are equal to prefix
|
||||
*/
|
||||
template <typename T>
|
||||
bool starts_with(const std::vector<T> &col, const std::vector<T> &prefix)
|
||||
template <typename T> bool starts_with(const T &con, const T &prefix)
|
||||
{
|
||||
if (prefix.size() > col.size())
|
||||
if (prefix.size() > con.size())
|
||||
return false;
|
||||
|
||||
return std::vector<std::string>(prefix.begin(), prefix.end()) ==
|
||||
std::vector<std::string>(col.begin(), col.begin() + prefix.size());
|
||||
return T(prefix.begin(), prefix.end()) ==
|
||||
T(con.begin(), con.begin() + prefix.size());
|
||||
}
|
||||
|
||||
template <>
|
||||
bool starts_with(
|
||||
const std::filesystem::path &path, const std::filesystem::path &prefix);
|
||||
|
||||
template <typename T>
|
||||
bool ends_with(const std::vector<T> &col, const std::vector<T> &suffix)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user