Added initial support for package diagrams

This commit is contained in:
Bartek Kryza
2022-01-22 21:01:33 +01:00
parent d6061dddff
commit 20759f412d
25 changed files with 1486 additions and 7 deletions

6
.gitignore vendored
View File

@@ -18,3 +18,9 @@ bin/
/debug/ /debug/
/release/ /release/
/.cache /.cache
# CLion
.idea/
cmake-build-debug/

View File

@@ -414,7 +414,7 @@ clanguml::class_diagram::model::diagram generate(
cppast::libclang_compilation_database &db, const std::string &name, cppast::libclang_compilation_database &db, const std::string &name,
clanguml::config::class_diagram &diagram) clanguml::config::class_diagram &diagram)
{ {
spdlog::info("Generating diagram {}.puml", name); LOG_DBG("Generating diagram {}.puml", name);
clanguml::class_diagram::model::diagram d; clanguml::class_diagram::model::diagram d;
d.set_name(name); d.set_name(name);
@@ -422,7 +422,7 @@ clanguml::class_diagram::model::diagram generate(
// configuration // configuration
std::vector<std::string> translation_units{}; std::vector<std::string> translation_units{};
for (const auto &g : diagram.glob) { for (const auto &g : diagram.glob) {
spdlog::debug("Processing glob: {}", g); LOG_DBG("Processing glob: {}", g);
const auto matches = glob::rglob(g); const auto matches = glob::rglob(g);
std::copy(matches.begin(), matches.end(), std::copy(matches.begin(), matches.end(),
std::back_inserter(translation_units)); std::back_inserter(translation_units));

View File

@@ -69,7 +69,7 @@ bool diagram::should_include(const std::string &name_) const
for (const auto &ex : exclude.namespaces) { for (const auto &ex : exclude.namespaces) {
if (name.find(ex) == 0) { if (name.find(ex) == 0) {
spdlog::debug("Skipping from diagram: {}", name); LOG_DBG("Skipping from diagram: {}", name);
return false; return false;
} }
} }
@@ -132,6 +132,7 @@ using clanguml::common::model::scope_t;
using clanguml::config::class_diagram; using clanguml::config::class_diagram;
using clanguml::config::config; using clanguml::config::config;
using clanguml::config::filter; using clanguml::config::filter;
using clanguml::config::package_diagram;
using clanguml::config::plantuml; using clanguml::config::plantuml;
using clanguml::config::sequence_diagram; using clanguml::config::sequence_diagram;
using clanguml::config::source_location; using clanguml::config::source_location;
@@ -282,6 +283,19 @@ template <> struct convert<sequence_diagram> {
} }
}; };
//
// class_diagram Yaml decoder
//
template <> struct convert<package_diagram> {
static bool decode(const Node &node, package_diagram &rhs)
{
if (!decode_diagram(node, rhs))
return false;
return true;
}
};
// //
// config Yaml decoder // config Yaml decoder
// //
@@ -313,8 +327,12 @@ template <> struct convert<config> {
rhs.diagrams[name] = std::make_shared<sequence_diagram>( rhs.diagrams[name] = std::make_shared<sequence_diagram>(
d.second.as<sequence_diagram>()); d.second.as<sequence_diagram>());
} }
else if (diagram_type == "package") {
rhs.diagrams[name] = std::make_shared<package_diagram>(
d.second.as<package_diagram>());
}
else { else {
spdlog::warn( LOG_WARN(
"Diagrams of type {} are not supported at the moment... ", "Diagrams of type {} are not supported at the moment... ",
diagram_type); diagram_type);
} }

View File

@@ -101,6 +101,10 @@ struct sequence_diagram : public diagram {
std::vector<source_location> start_from; std::vector<source_location> start_from;
}; };
struct package_diagram : public diagram {
virtual ~package_diagram() = default;
};
struct config { struct config {
// the glob list is additive and relative to the current // the glob list is additive and relative to the current
// directory // directory

View File

@@ -16,6 +16,7 @@
* limitations under the License. * limitations under the License.
*/ */
#include "compilation_database.h" #include "compilation_database.h"
#include "util/util.h"
#include <filesystem> #include <filesystem>
#include <spdlog/fmt/fmt.h> #include <spdlog/fmt/fmt.h>
@@ -73,7 +74,7 @@ CXTranslationUnit compilation_database::parse_translation_unit(
clang_CompileCommands_getCommand(compile_commands, 0); clang_CompileCommands_getCommand(compile_commands, 0);
auto cc_filename = clang_CompileCommand_getFilename(compile_command); auto cc_filename = clang_CompileCommand_getFilename(compile_command);
spdlog::debug( LOG_DBG(
"Processing compile command file: {}", clang_getCString(cc_filename)); "Processing compile command file: {}", clang_getCString(cc_filename));
auto num_args = clang_CompileCommand_getNumArgs(compile_command); auto num_args = clang_CompileCommand_getNumArgs(compile_command);
@@ -84,7 +85,7 @@ CXTranslationUnit compilation_database::parse_translation_unit(
arguments = (char **)malloc(sizeof(char *) * num_args); arguments = (char **)malloc(sizeof(char *) * num_args);
for (j = 0; j < num_args; ++j) { for (j = 0; j < num_args; ++j) {
CXString arg = clang_CompileCommand_getArg(compile_command, j); CXString arg = clang_CompileCommand_getArg(compile_command, j);
spdlog::debug("Processing argument: {}", clang_getCString(arg)); LOG_DBG("Processing argument: {}", clang_getCString(arg));
arguments[j] = strdup(clang_getCString(arg)); arguments[j] = strdup(clang_getCString(arg));
clang_disposeString(arg); clang_disposeString(arg);
} }

View File

@@ -56,7 +56,6 @@ std::string ns(const cppast::cpp_entity &e);
std::string ns(const cppast::cpp_type &t, const cppast::cpp_entity_index &idx); std::string ns(const cppast::cpp_type &t, const cppast::cpp_entity_index &idx);
bool is_inside_class(const cppast::cpp_entity &e); bool is_inside_class(const cppast::cpp_entity &e);
} // namespace util } // namespace util
} // namespace cx } // namespace cx
} // namespace clanguml } // namespace clanguml

View File

@@ -21,7 +21,9 @@
#include "class_diagram/generators/plantuml/class_diagram_generator.h" #include "class_diagram/generators/plantuml/class_diagram_generator.h"
#include "config/config.h" #include "config/config.h"
#include "cx/compilation_database.h" #include "cx/compilation_database.h"
#include "package_diagram/generators/plantuml/package_diagram_generator.h"
#include "sequence_diagram/generators/plantuml/sequence_diagram_generator.h" #include "sequence_diagram/generators/plantuml/sequence_diagram_generator.h"
#include "util/util.h" #include "util/util.h"
#include <cli11/CLI11.hpp> #include <cli11/CLI11.hpp>
@@ -79,6 +81,7 @@ int main(int argc, const char *argv[])
continue; continue;
using clanguml::config::class_diagram; using clanguml::config::class_diagram;
using clanguml::config::package_diagram;
using clanguml::config::sequence_diagram; using clanguml::config::sequence_diagram;
std::filesystem::path path{"puml/" + name + ".puml"}; std::filesystem::path path{"puml/" + name + ".puml"};
@@ -103,6 +106,15 @@ int main(int argc, const char *argv[])
dynamic_cast<clanguml::config::sequence_diagram &>(*diagram), dynamic_cast<clanguml::config::sequence_diagram &>(*diagram),
model); model);
} }
else if (std::dynamic_pointer_cast<package_diagram>(diagram)) {
auto model =
clanguml::package_diagram::generators::plantuml::generate(
db, name, dynamic_cast<package_diagram &>(*diagram));
ofs << clanguml::package_diagram::generators::plantuml::generator(
dynamic_cast<clanguml::config::package_diagram &>(*diagram),
model);
}
ofs.close(); ofs.close();
} }

View File

@@ -0,0 +1,199 @@
/**
* src/class_diagram/generators/plantuml/class_diagram_generator.cc
*
* Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "package_diagram_generator.h"
#include "util/error.h"
namespace clanguml::package_diagram::generators::plantuml {
std::string relative_to(std::string n, std::string c)
{
if (c.rfind(n) == std::string::npos)
return c;
return c.substr(n.size() + 2);
}
generator::generator(
clanguml::config::package_diagram &config, diagram_model &model)
: m_config(config)
, m_model(model)
{
}
std::string generator::to_string(relationship_t r, std::string style) const
{
switch (r) {
case relationship_t::kOwnership:
case relationship_t::kComposition:
return style.empty() ? "*--" : fmt::format("*-[{}]-", style);
case relationship_t::kAggregation:
return style.empty() ? "o--" : fmt::format("o-[{}]-", style);
case relationship_t::kContainment:
return style.empty() ? "--+" : fmt::format("-[{}]-+", style);
case relationship_t::kAssociation:
return style.empty() ? "-->" : fmt::format("-[{}]->", style);
case relationship_t::kInstantiation:
return style.empty() ? "..|>" : fmt::format(".[{}].|>", style);
case relationship_t::kFriendship:
return style.empty() ? "<.." : fmt::format("<.[{}].", style);
case relationship_t::kDependency:
return style.empty() ? "..>" : fmt::format(".[{}].>", style);
default:
return "";
}
}
std::string generator::name(relationship_t r) const
{
switch (r) {
case relationship_t::kOwnership:
case relationship_t::kComposition:
return "composition";
case relationship_t::kAggregation:
return "aggregation";
case relationship_t::kContainment:
return "containment";
case relationship_t::kAssociation:
return "association";
case relationship_t::kDependency:
return "dependency";
default:
return "unknown";
}
}
void generator::generate(const package &p, std::ostream &ostr) const
{
const auto uns = m_config.using_namespace;
ostr << "component [" << p.name() << "] as " << p.alias();
if (!p.style().empty())
ostr << " " << p.style();
ostr << " {" << '\n';
// C++17 cannot figure out to use cbegin/cend in a for-range loop on const
// collection... ¯\_(ツ)_/¯
for (auto subpackage = p.cbegin(); subpackage != p.cend(); subpackage++) {
generate(**subpackage, ostr);
}
ostr << "}" << '\n';
//
// Process notes
//
for (auto decorator : p.decorators()) {
auto note = std::dynamic_pointer_cast<decorators::note>(decorator);
if (note && note->applies_to_diagram(m_config.name)) {
ostr << "note " << note->position << " of " << p.alias() << '\n'
<< note->text << '\n'
<< "end note\n";
}
}
//
// // Print relationships
// ostr << all_relations_str.str();
}
void generator::generate(std::ostream &ostr) const
{
ostr << "@startuml" << '\n';
// Process aliases in any of the puml directives
for (const auto &b : m_config.puml.before) {
std::string note{b};
std::tuple<std::string, size_t, size_t> alias_match;
while (util::find_element_alias(note, alias_match)) {
auto alias = m_model.to_alias(ns_relative(
m_config.using_namespace, std::get<0>(alias_match)));
note.replace(
std::get<1>(alias_match), std::get<2>(alias_match), alias);
}
ostr << note << '\n';
}
if (m_config.should_include_entities("packages")) {
for (const auto &p : m_model) {
generate(*p, ostr);
ostr << '\n';
}
}
// Process aliases in any of the puml directives
for (const auto &b : m_config.puml.after) {
std::string note{b};
std::tuple<std::string, size_t, size_t> alias_match;
while (util::find_element_alias(note, alias_match)) {
auto full_name =
fmt::format("{}::{}", fmt::join(m_config.using_namespace, "::"),
ns_relative(
m_config.using_namespace, std::get<0>(alias_match)));
auto alias = m_model.to_alias(full_name);
note.replace(
std::get<1>(alias_match), std::get<2>(alias_match), alias);
}
ostr << note << '\n';
}
ostr << "@enduml" << '\n';
}
std::ostream &operator<<(std::ostream &os, const generator &g)
{
g.generate(os);
return os;
}
clanguml::package_diagram::model::diagram generate(
cppast::libclang_compilation_database &db, const std::string &name,
clanguml::config::package_diagram &diagram)
{
LOG_INFO("Generating package diagram {}.puml", name);
clanguml::package_diagram::model::diagram d;
d.set_name(name);
// Get all translation units matching the glob from diagram
// configuration
std::vector<std::string> translation_units{};
for (const auto &g : diagram.glob) {
LOG_DBG("Processing glob: {}", g);
const auto matches = glob::rglob(g);
std::copy(matches.begin(), matches.end(),
std::back_inserter(translation_units));
}
cppast::cpp_entity_index idx;
cppast::simple_file_parser<cppast::libclang_parser> parser{
type_safe::ref(idx)};
// Process all matching translation units
clanguml::package_diagram::visitor::translation_unit_visitor ctx(
idx, d, diagram);
cppast::parse_files(parser, translation_units, db);
for (auto &file : parser.files())
ctx(file);
return d;
}
}

View File

@@ -0,0 +1,79 @@
/**
* src/package_diagram/generators/plantuml/package_diagram_generator.h
*
* Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "package_diagram/model/package.h"
#include "package_diagram/visitor/translation_unit_visitor.h"
#include "common/model/relationship.h"
#include "config/config.h"
#include "cx/compilation_database.h"
#include "package_diagram/model/diagram.h"
#include "util/util.h"
#include <cppast/cpp_entity_index.hpp>
#include <cppast/libclang_parser.hpp>
#include <glob/glob.hpp>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <sstream>
namespace clanguml {
namespace package_diagram {
namespace generators {
namespace plantuml {
using diagram_config = clanguml::package_diagram::model::diagram;
using diagram_model = clanguml::package_diagram::model::diagram;
using clanguml::common::model::relationship_t;
using clanguml::common::model::scope_t;
using clanguml::package_diagram::model::package;
using namespace clanguml::util;
std::string relative_to(std::string n, std::string c);
class generator {
public:
generator(clanguml::config::package_diagram &config, diagram_model &model);
std::string to_string(relationship_t r, std::string style = "") const;
std::string name(relationship_t r) const;
void generate_alias(const package &c, std::ostream &ostr) const;
void generate(const package &e, std::ostream &ostr) const;
void generate(std::ostream &ostr) const;
friend std::ostream &operator<<(std::ostream &os, const generator &g);
private:
clanguml::config::package_diagram &m_config;
diagram_model &m_model;
};
clanguml::package_diagram::model::diagram generate(
cppast::libclang_compilation_database &db, const std::string &name,
clanguml::config::package_diagram &diagram);
}
}
}
}

View File

@@ -0,0 +1,48 @@
/**
* src/package_diagram/model/diagram.cc
*
* Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "diagram.h"
#include "util/error.h"
#include "util/util.h"
namespace clanguml::package_diagram::model {
std::string diagram::name() const { return name_; }
void diagram::set_name(const std::string &name) { name_ = name; }
std::string diagram::to_alias(const std::string &full_name) const
{
LOG_DBG("Looking for alias for {}", full_name);
auto fn = util::split(full_name, "::");
if (fn.empty())
throw error::uml_alias_missing(
fmt::format("Missing alias for '{}'", full_name));
auto package = get_package(fn);
if (!package)
throw error::uml_alias_missing(
fmt::format("Missing alias for '{}'", full_name));
return package.value().alias();
}
}

View File

@@ -0,0 +1,42 @@
/**
* src/package_diagram/model/diagram.h
*
* Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "package.h"
#include <type_safe/optional_ref.hpp>
#include <string>
#include <vector>
namespace clanguml::package_diagram::model {
class diagram : public detail::package_trait<package, std::vector> {
public:
std::string name() const;
void set_name(const std::string &name);
std::string to_alias(const std::string &full_name) const;
private:
std::string name_;
std::vector<std::unique_ptr<package>> packages_;
};
}

View File

@@ -0,0 +1,50 @@
/**
* src/package_diagram/model/class.h
*
* Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "package.h"
#include "util/util.h"
#include <sstream>
namespace clanguml::package_diagram::model {
package::package(const std::vector<std::string> &using_namespaces)
: element{using_namespaces}
{
}
std::string package::full_name(bool relative) const
{
auto fn = get_namespace();
auto ns = using_namespaces();
if (relative && (fn.size() >= ns.size())) {
if(starts_with(fn, ns))
fn = std::vector<std::string>(fn.begin() + ns.size(), fn.end());
}
fn.push_back(name());
return fmt::format("{}", fmt::join(fn, "::"));
}
bool operator==(const package &l, const package &r)
{
return l.full_name(false) == r.full_name(false);
}
}

View File

@@ -0,0 +1,121 @@
/**
* src/package_diagram/model/class.h
*
* Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "util/util.h"
#include "common/model/element.h"
#include "common/model/stylable_element.h"
#include <spdlog/spdlog.h>
#include <type_safe/optional_ref.hpp>
#include <string>
#include <vector>
namespace clanguml::package_diagram::model {
namespace detail {
template <typename T, template <typename> class Container,
typename Ptr = std::unique_ptr<T>>
class package_trait {
public:
void add_package(std::unique_ptr<T> p)
{
packages_.emplace_back(std::move(p));
}
void add_package(std::vector<std::string> path, std::unique_ptr<T> p)
{
assert(p);
LOG_DBG(
"Adding package {} at path '{}'", p->name(), fmt::join(path, "::"));
if (path.empty()) {
add_package(std::move(p));
return;
}
auto parent = get_package(path);
if (parent)
parent.value().add_package(std::move(p));
else
spdlog::error(
"No parent package found at: {}", fmt::join(path, "::"));
}
type_safe::optional_ref<T> get_package(std::vector<std::string> path) const
{
LOG_DBG("Getting package at path: {}", fmt::join(path, "::"));
if (path.empty() || !has_package(path.at(0)))
return {};
auto p = get_package(path.at(0));
if (path.size() == 1)
return p;
return p.value().get_package(
std::vector<std::string>(path.begin() + 1, path.end()));
}
type_safe::optional_ref<T> get_package(const std::string &name) const
{
auto it = std::find_if(packages_.cbegin(), packages_.cend(),
[&](const auto &p) { return name == p->name(); });
if (it == packages_.end())
return {};
assert(it->get() != nullptr);
return type_safe::ref(*(it->get()));
}
bool has_package(const std::string &name) const
{
return std::find_if(packages_.cbegin(), packages_.cend(),
[&](const auto &p) { return name == p->name(); }) !=
packages_.end();
}
typedef typename Container<Ptr>::iterator iterator;
typedef typename Container<Ptr>::const_iterator const_iterator;
inline iterator begin() noexcept { return packages_.begin(); }
inline const_iterator cbegin() const noexcept { return packages_.cbegin(); }
inline iterator end() noexcept { return packages_.end(); }
inline const_iterator cend() const noexcept { return packages_.cend(); }
protected:
Container<std::unique_ptr<T>> packages_;
};
}
class package : public common::model::element,
public common::model::stylable_element,
public detail::package_trait<package, std::vector> {
public:
package(const std::vector<std::string> &using_namespaces);
std::string full_name(bool relative) const override;
friend bool operator==(const package &l, const package &r);
};
}

View File

@@ -0,0 +1,43 @@
/**
* src/package_diagram/model/visitor/element_visitor_context.cc
*
* Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "element_visitor_context.h"
#include "translation_unit_context.h"
namespace clanguml::package_diagram::visitor {
template <typename T>
element_visitor_context<T>::element_visitor_context(
clanguml::package_diagram::model::diagram &diagram, T &element)
: element_{element}
, diagram_{diagram}
{
}
template <typename T> T &element_visitor_context<T>::element()
{
return element_;
}
template <typename T>
clanguml::package_diagram::model::diagram &element_visitor_context<T>::diagram()
{
return diagram_;
}
}

View File

@@ -0,0 +1,42 @@
/**
* src/package_diagram/model/visitor/element_visitor_context.h
*
* Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "package_diagram/model/diagram.h"
namespace clanguml::package_diagram::visitor {
class translation_unit_context;
template <typename T> class element_visitor_context {
public:
element_visitor_context(
clanguml::package_diagram::model::diagram &diagram, T &element);
T &element();
clanguml::package_diagram::model::diagram &diagram();
private:
translation_unit_context *ctx_;
T &element_;
clanguml::package_diagram::model::diagram &diagram_;
};
}

View File

@@ -0,0 +1,136 @@
/**
* src/package_diagram/visitor/translation_unit_context.cc
*
* Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "translation_unit_context.h"
#include "cx/util.h"
namespace clanguml::package_diagram::visitor {
translation_unit_context::translation_unit_context(
cppast::cpp_entity_index &idx,
clanguml::package_diagram::model::diagram &diagram,
const clanguml::config::package_diagram &config)
: entity_index_{idx}
, diagram_{diagram}
, config_{config}
{
}
bool translation_unit_context::has_type_alias(
const std::string &full_name) const
{
bool res = alias_index_.find(full_name) != alias_index_.end();
LOG_DBG("Alias {} {} found in index", full_name, res ? "" : "not");
return res;
}
void translation_unit_context::add_type_alias(const std::string &full_name,
type_safe::object_ref<const cppast::cpp_type> &&ref)
{
if (!has_type_alias(full_name)) {
LOG_DBG("Stored type alias: {} -> {} ", full_name,
cppast::to_string(ref.get()));
alias_index_.emplace(full_name, std::move(ref));
}
}
type_safe::object_ref<const cppast::cpp_type>
translation_unit_context::get_type_alias(const std::string &full_name) const
{
assert(has_type_alias(full_name));
return alias_index_.at(full_name);
}
type_safe::object_ref<const cppast::cpp_type>
translation_unit_context::get_type_alias_final(const cppast::cpp_type &t) const
{
const auto type_full_name =
cx::util::full_name(cppast::remove_cv(t), entity_index_, false);
if (has_type_alias(type_full_name)) {
return get_type_alias_final(alias_index_.at(type_full_name).get());
}
return type_safe::ref(t);
}
bool translation_unit_context::has_type_alias_template(
const std::string &full_name) const
{
bool res =
alias_template_index_.find(full_name) != alias_template_index_.end();
LOG_DBG("Alias template {} {} found in index", full_name, res ? "" : "not");
return res;
}
void translation_unit_context::add_type_alias_template(
const std::string &full_name,
type_safe::object_ref<const cppast::cpp_type> &&ref)
{
if (!has_type_alias_template(full_name)) {
LOG_DBG("Stored type alias template for: {} ", full_name);
alias_template_index_.emplace(full_name, std::move(ref));
}
}
type_safe::object_ref<const cppast::cpp_type>
translation_unit_context::get_type_alias_template(
const std::string &full_name) const
{
assert(has_type_alias_template(full_name));
return alias_template_index_.at(full_name);
}
void translation_unit_context::push_namespace(const std::string &ns)
{
namespace_.push_back(ns);
}
void translation_unit_context::pop_namespace() { namespace_.pop_back(); }
const std::vector<std::string> &translation_unit_context::get_namespace() const
{
return namespace_;
}
const cppast::cpp_entity_index &translation_unit_context::entity_index() const
{
return entity_index_;
}
const clanguml::config::package_diagram &
translation_unit_context::config() const
{
return config_;
}
clanguml::package_diagram::model::diagram &translation_unit_context::diagram()
{
return diagram_;
}
}

View File

@@ -0,0 +1,88 @@
/**
* src/package_diagram/visitor/translation_unit_context.h
*
* Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "config/config.h"
#include "package_diagram/model/diagram.h"
#include <cppast/cpp_entity_index.hpp>
#include <cppast/cpp_type.hpp>
#include <type_safe/reference.hpp>
namespace clanguml::package_diagram::visitor {
class translation_unit_context {
public:
translation_unit_context(cppast::cpp_entity_index &idx,
clanguml::package_diagram::model::diagram &diagram,
const clanguml::config::package_diagram &config);
bool has_type_alias(const std::string &full_name) const;
void add_type_alias(const std::string &full_name,
type_safe::object_ref<const cppast::cpp_type> &&ref);
type_safe::object_ref<const cppast::cpp_type> get_type_alias(
const std::string &full_name) const;
type_safe::object_ref<const cppast::cpp_type> get_type_alias_final(
const cppast::cpp_type &t) const;
bool has_type_alias_template(const std::string &full_name) const;
void add_type_alias_template(const std::string &full_name,
type_safe::object_ref<const cppast::cpp_type> &&ref);
type_safe::object_ref<const cppast::cpp_type> get_type_alias_template(
const std::string &full_name) const;
void push_namespace(const std::string &ns);
void pop_namespace();
const std::vector<std::string> &get_namespace() const;
const cppast::cpp_entity_index &entity_index() const;
const clanguml::config::package_diagram &config() const;
clanguml::package_diagram::model::diagram &diagram();
private:
// Current visitor namespace
std::vector<std::string> namespace_;
// Reference to the cppast entity index
cppast::cpp_entity_index &entity_index_;
// Reference to the output diagram model
clanguml::package_diagram::model::diagram &diagram_;
// Reference to class diagram config
const clanguml::config::package_diagram &config_;
// Map of discovered aliases (declared with 'using' keyword)
std::map<std::string, type_safe::object_ref<const cppast::cpp_type>>
alias_index_;
// Map of discovered template aliases (declared with 'using' keyword)
std::map<std::string, type_safe::object_ref<const cppast::cpp_type>>
alias_template_index_;
};
}

View File

@@ -0,0 +1,352 @@
/**
* src/package_diagram/visitor/translation_unit_visitor.cc
*
* Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "translation_unit_visitor.h"
#include <cppast/cpp_alias_template.hpp>
#include <cppast/cpp_array_type.hpp>
#include <cppast/cpp_class_template.hpp>
#include <cppast/cpp_entity_kind.hpp>
#include <cppast/cpp_enum.hpp>
#include <cppast/cpp_friend.hpp>
#include <cppast/cpp_member_function.hpp>
#include <cppast/cpp_member_variable.hpp>
#include <cppast/cpp_namespace.hpp>
#include <cppast/cpp_template.hpp>
#include <cppast/cpp_type_alias.hpp>
#include <cppast/cpp_variable.hpp>
#include <spdlog/spdlog.h>
#include <deque>
namespace clanguml::package_diagram::visitor {
using clanguml::class_diagram::model::type_alias;
using clanguml::common::model::access_t;
using clanguml::common::model::relationship;
using clanguml::common::model::relationship_t;
using clanguml::common::model::scope_t;
using clanguml::package_diagram::model::diagram;
using clanguml::package_diagram::model::package;
namespace detail {
scope_t cpp_access_specifier_to_scope(
cppast::cpp_access_specifier_kind access_specifier)
{
scope_t scope = scope_t::kPublic;
switch (access_specifier) {
case cppast::cpp_access_specifier_kind::cpp_public:
scope = scope_t::kPublic;
break;
case cppast::cpp_access_specifier_kind::cpp_private:
scope = scope_t::kPrivate;
break;
case cppast::cpp_access_specifier_kind::cpp_protected:
scope = scope_t::kProtected;
break;
default:
break;
}
return scope;
}
}
translation_unit_visitor::translation_unit_visitor(
cppast::cpp_entity_index &idx,
clanguml::package_diagram::model::diagram &diagram,
const clanguml::config::package_diagram &config)
: ctx{idx, diagram, config}
{
}
void translation_unit_visitor::operator()(const cppast::cpp_entity &file)
{
cppast::visit(file,
[&, this](const cppast::cpp_entity &e, cppast::visitor_info info) {
if (e.kind() == cppast::cpp_entity_kind::namespace_t) {
if (info.event ==
cppast::visitor_info::container_entity_enter) {
LOG_DBG("========== Visiting '{}' - {}", e.name(),
cppast::to_string(e.kind()));
const auto &ns_declaration =
static_cast<const cppast::cpp_namespace &>(e);
if (!ns_declaration.is_anonymous() &&
!ns_declaration.is_inline()) {
auto p = std::make_unique<package>(
ctx.config().using_namespace);
p->set_name(e.name());
p->set_namespace(ctx.get_namespace());
ctx.diagram().add_package(
ctx.get_namespace(), std::move(p));
ctx.push_namespace(e.name());
}
}
else {
LOG_DBG("========== Leaving '{}' - {}", e.name(),
cppast::to_string(e.kind()));
const auto &ns_declaration =
static_cast<const cppast::cpp_namespace &>(e);
if (!ns_declaration.is_anonymous() &&
!ns_declaration.is_inline())
ctx.pop_namespace();
}
}
else if (e.kind() ==
cppast::cpp_entity_kind::class_template_specialization_t) {
LOG_DBG("========== Visiting '{}' - {}",
cx::util::full_name(ctx.get_namespace(), e),
cppast::to_string(e.kind()));
auto &tspec = static_cast<
const cppast::cpp_class_template_specialization &>(e);
process_class_declaration(
tspec.class_(), type_safe::ref(tspec));
}
else if (e.kind() == cppast::cpp_entity_kind::class_t) {
LOG_DBG("========== Visiting '{}' - {}",
cx::util::full_name(ctx.get_namespace(), e),
cppast::to_string(e.kind()));
auto &cls = static_cast<const cppast::cpp_class &>(e);
if (cppast::get_definition(ctx.entity_index(), cls)) {
auto &clsdef = static_cast<const cppast::cpp_class &>(
cppast::get_definition(ctx.entity_index(), cls)
.value());
if (&cls != &clsdef) {
LOG_DBG("Forward declaration of class {} - skipping...",
cls.name());
return;
}
}
if (ctx.config().should_include(
cx::util::fully_prefixed(ctx.get_namespace(), cls)))
process_class_declaration(cls);
}
// else if (e.kind() == cppast::cpp_entity_kind::enum_t)
// {
// LOG_DBG("========== Visiting '{}' - {}",
// cx::util::full_name(ctx.get_namespace(), e),
// cppast::to_string(e.kind()));
//
// auto &enm = static_cast<const cppast::cpp_enum
// &>(e);
//
// if (ctx.config().should_include(
// cx::util::fully_prefixed(ctx.get_namespace(),
// enm)))
// process_enum_declaration(enm);
// }
else if (e.kind() == cppast::cpp_entity_kind::type_alias_t) {
LOG_DBG("========== Visiting '{}' - {}",
cx::util::full_name(ctx.get_namespace(), e),
cppast::to_string(e.kind()));
auto &ta = static_cast<const cppast::cpp_type_alias &>(e);
type_alias t;
t.set_alias(cx::util::full_name(ctx.get_namespace(), ta));
t.set_underlying_type(cx::util::full_name(ta.underlying_type(),
ctx.entity_index(), cx::util::is_inside_class(e)));
ctx.add_type_alias(cx::util::full_name(ctx.get_namespace(), ta),
type_safe::ref(ta.underlying_type()));
// ctx.diagram().add_type_alias(std::move(t));
}
else if (e.kind() == cppast::cpp_entity_kind::alias_template_t) {
LOG_DBG("========== Visiting '{}' - {}",
cx::util::full_name(ctx.get_namespace(), e),
cppast::to_string(e.kind()));
auto &at = static_cast<const cppast::cpp_alias_template &>(e);
// if (at.type_alias().underlying_type().kind()
// ==
// cppast::cpp_type_kind::unexposed_t) {
// LOG_WARN("Template alias has unexposed
// underlying type: {}",
// static_cast<const
// cppast::cpp_unexposed_type &>(
// at.type_alias().underlying_type())
// .name());
// }
// else {
// class_ tinst =
// build_template_instantiation(static_cast<
// const
// cppast::cpp_template_instantiation_type
// &>(
// at.type_alias().underlying_type()));
//
// ctx.diagram().add_class(std::move(tinst));
// }
}
});
}
void translation_unit_visitor::process_class_declaration(
const cppast::cpp_class &cls,
type_safe::optional_ref<const cppast::cpp_template_specialization> tspec)
{
return;
/*
class_ c{ctx.config().using_namespace};
c.is_struct(cls.class_kind() == cppast::cpp_class_kind::struct_t);
c.set_name(cx::util::full_name(ctx.get_namespace(), cls));
if (cls.comment().has_value())
c.add_decorators(decorators::parse(cls.comment().value()));
cppast::cpp_access_specifier_kind last_access_specifier =
cppast::cpp_access_specifier_kind::cpp_private;
// Process class documentation comment
if (cppast::is_templated(cls)) {
if (cls.parent().value().comment().has_value())
c.add_decorators(
decorators::parse(cls.parent().value().comment().value()));
}
else {
if (cls.comment().has_value())
c.add_decorators(decorators::parse(cls.comment().value()));
}
if (c.skip())
return;
c.set_style(c.style_spec());
// Process class child entities
if (c.is_struct())
last_access_specifier = cppast::cpp_access_specifier_kind::cpp_public;
for (auto &child : cls) {
if (child.kind() == cppast::cpp_entity_kind::access_specifier_t) {
auto &as = static_cast<const cppast::cpp_access_specifier &>(child);
last_access_specifier = as.access_specifier();
}
else if (child.kind() == cppast::cpp_entity_kind::member_variable_t) {
auto &mv = static_cast<const cppast::cpp_member_variable &>(child);
process_field(mv, c, last_access_specifier);
}
else if (child.kind() == cppast::cpp_entity_kind::variable_t) {
auto &mv = static_cast<const cppast::cpp_variable &>(child);
process_static_field(mv, c, last_access_specifier);
}
else if (child.kind() == cppast::cpp_entity_kind::member_function_t) {
auto &mf = static_cast<const cppast::cpp_member_function &>(child);
process_method(mf, c, last_access_specifier);
}
else if (child.kind() == cppast::cpp_entity_kind::function_t) {
auto &mf = static_cast<const cppast::cpp_function &>(child);
process_static_method(mf, c, last_access_specifier);
}
else if (child.kind() == cppast::cpp_entity_kind::function_template_t) {
auto &tm =
static_cast<const cppast::cpp_function_template &>(child);
process_template_method(tm, c, last_access_specifier);
}
else if (child.kind() == cppast::cpp_entity_kind::constructor_t) {
auto &mc = static_cast<const cppast::cpp_constructor &>(child);
process_constructor(mc, c, last_access_specifier);
}
else if (child.kind() == cppast::cpp_entity_kind::destructor_t) {
auto &mc = static_cast<const cppast::cpp_destructor &>(child);
process_destructor(mc, c, last_access_specifier);
}
else if (child.kind() == cppast::cpp_entity_kind::enum_t) {
auto &en = static_cast<const cppast::cpp_enum &>(child);
if (en.name().empty()) {
// Here we only want to handle anonymous enums, regular nested
// enums are handled in the file-level visitor
process_anonymous_enum(en, c, last_access_specifier);
}
}
else if (child.kind() == cppast::cpp_entity_kind::friend_t) {
auto &fr = static_cast<const cppast::cpp_friend &>(child);
LOG_DBG("Found friend declaration: {}, {}", child.name(),
child.scope_name() ? child.scope_name().value().name()
: "<no-scope>");
process_friend(fr, c);
}
else if (cppast::is_friended(child)) {
auto &fr =
static_cast<const cppast::cpp_friend &>(child.parent().value());
LOG_DBG("Found friend template: {}", child.name());
process_friend(fr, c);
}
else {
LOG_DBG("Found some other class child: {} ({})", child.name(),
cppast::to_string(child.kind()));
}
}
// Process class bases
for (auto &base : cls.bases()) {
class_parent cp;
cp.set_name(
clanguml::cx::util::fully_prefixed(ctx.get_namespace(), base));
cp.is_virtual(base.is_virtual());
switch (base.access_specifier()) {
case cppast::cpp_access_specifier_kind::cpp_private:
cp.set_access(access_t::kPrivate);
break;
case cppast::cpp_access_specifier_kind::cpp_public:
cp.set_access(access_t::kPublic);
break;
case cppast::cpp_access_specifier_kind::cpp_protected:
cp.set_access(access_t::kProtected);
break;
default:
cp.set_access(access_t::kPublic);
}
LOG_DBG("Found base class {} for class {}", cp.name(), c.name());
c.add_parent(std::move(cp));
}
ctx.diagram().add_class(std::move(c));
*/
}
const cppast::cpp_type &translation_unit_visitor::resolve_alias(
const cppast::cpp_type &type)
{
const auto &raw_type = cppast::remove_cv(cx::util::unreferenced(type));
const auto type_full_name =
cx::util::full_name(raw_type, ctx.entity_index(), false);
if (ctx.has_type_alias(type_full_name)) {
return ctx.get_type_alias_final(raw_type).get();
}
return type;
}
}

View File

@@ -0,0 +1,66 @@
/**
* src/package_diagram/visitor/translation_unit_visitor.h
*
* Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "package_diagram/model/diagram.h"
#include "package_diagram/visitor/translation_unit_context.h"
#include "config/config.h"
#include "cx/cursor.h"
#include <clang-c/CXCompilationDatabase.h>
#include <clang-c/Index.h>
#include <cppast/cpp_friend.hpp>
#include <cppast/cpp_function_template.hpp>
#include <cppast/cpp_member_function.hpp>
#include <cppast/cpp_member_variable.hpp>
#include <cppast/cpp_template.hpp>
#include <cppast/cpp_template_parameter.hpp>
#include <cppast/cpp_type.hpp>
#include <cppast/visitor.hpp>
#include <type_safe/reference.hpp>
#include <functional>
#include <map>
#include <memory>
#include <string>
namespace clanguml::package_diagram::visitor {
class translation_unit_visitor {
public:
translation_unit_visitor(cppast::cpp_entity_index &idx,
clanguml::package_diagram::model::diagram &diagram,
const clanguml::config::package_diagram &config);
void operator()(const cppast::cpp_entity &file);
void process_class_declaration(const cppast::cpp_class &cls,
type_safe::optional_ref<const cppast::cpp_template_specialization>
tspec = nullptr);
private:
/**
* Try to resolve a type instance into a type referenced through an alias.
* If t does not represent an alias, returns t.
*/
const cppast::cpp_type &resolve_alias(const cppast::cpp_type &t);
// ctx allows to track current visitor context, e.g. current namespace
translation_unit_context ctx;
};
}

View File

@@ -115,4 +115,34 @@ bool find_element_alias(
bool replace_all( bool replace_all(
std::string &input, std::string pattern, std::string replace_with); std::string &input, std::string pattern, std::string replace_with);
} }
/**
* @brief Appends a vector to a vector.
*
* @tparam T
* @param l
* @param r
*/
template <typename T> void append(std::vector<T> &l, const std::vector<T> &r)
{
l.insert(l.end(), r.begin(), r.end());
}
/**
* @brief Checks if vector starts with a prefix.
*
* @tparam T
* @param col
* @param prefix
* @return
*/
template <typename T>
bool starts_with(const std::vector<T> &col, const std::vector<T> &prefix)
{
if(prefix.size() > col.size())
return false;
return std::vector<std::string>(prefix.begin(), prefix.end()) ==
std::vector<std::string>(col.begin(), col.begin() + prefix.size());
}
} }

20
tests/t30001/.clang-uml Normal file
View File

@@ -0,0 +1,20 @@
compilation_database_dir: ..
output_directory: puml
diagrams:
t30001_package:
type: package
glob:
- ../../tests/t30001/t30001.cc
include:
namespaces:
- clanguml::t30001
exclude:
namespaces:
- clanguml::t30001::detail
using_namespace:
- clanguml::t30001
plantuml:
before:
- "' t30001 test package diagram"
after:
- "note left of @A(A::AA::AAA): A AAA note..."

25
tests/t30001/t30001.cc Normal file
View File

@@ -0,0 +1,25 @@
namespace clanguml {
namespace t30001 {
namespace A {
namespace AA {
namespace AAA {
} // namespace AAA
namespace BBB {
} // namespace BBB
} // namespace AA
namespace BB {
} // namespace BB
} // namespace A
namespace B {
namespace AA {
namespace AAA {
} // namespace AAA
namespace BBB {
} // namespace BBB
} // namespace AA
namespace BB {
} // namespace BB
} // namespace B
}
}

55
tests/t30001/test_case.h Normal file
View File

@@ -0,0 +1,55 @@
/**
* tests/t30001/test_case.cc
*
* Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
TEST_CASE("t30001", "[test-case][package]")
{
auto [config, db] = load_config("t30001");
auto diagram = config.diagrams["t30001_package"];
REQUIRE(diagram->include.namespaces.size() == 1);
REQUIRE_THAT(diagram->include.namespaces,
VectorContains(std::string{"clanguml::t30001"}));
REQUIRE(diagram->exclude.namespaces.size() == 1);
REQUIRE_THAT(diagram->exclude.namespaces,
VectorContains(std::string{"clanguml::t30001::detail"}));
REQUIRE(diagram->should_include("clanguml::t30001::A"));
REQUIRE(!diagram->should_include("clanguml::t30001::detail::C"));
REQUIRE(!diagram->should_include("std::vector"));
REQUIRE(diagram->name == "t30001_package");
auto model = generate_package_diagram(db, diagram);
REQUIRE(model.name() == "t30001_package");
auto puml = generate_package_puml(diagram, model);
AliasMatcher _A(puml);
REQUIRE_THAT(puml, StartsWith("@startuml"));
REQUIRE_THAT(puml, EndsWith("@enduml\n"));
REQUIRE_THAT(puml, Contains("component [A]"));
REQUIRE_THAT(puml, Contains("component [AA]"));
REQUIRE_THAT(puml, Contains("component [AAA]"));
save_puml(
"./" + config.output_directory + "/" + diagram->name + ".puml", puml);
}

View File

@@ -64,6 +64,18 @@ clanguml::class_diagram::model::diagram generate_class_diagram(
return diagram_model; return diagram_model;
} }
clanguml::package_diagram::model::diagram generate_package_diagram(
cppast::libclang_compilation_database &db,
std::shared_ptr<clanguml::config::diagram> diagram)
{
auto diagram_model =
clanguml::package_diagram::generators::plantuml::generate(db,
diagram->name,
dynamic_cast<clanguml::config::package_diagram &>(*diagram));
return diagram_model;
}
std::string generate_sequence_puml( std::string generate_sequence_puml(
std::shared_ptr<clanguml::config::diagram> config, std::shared_ptr<clanguml::config::diagram> config,
clanguml::sequence_diagram::model::diagram &model) clanguml::sequence_diagram::model::diagram &model)
@@ -92,6 +104,20 @@ std::string generate_class_puml(
return ss.str(); return ss.str();
} }
std::string generate_package_puml(
std::shared_ptr<clanguml::config::diagram> config,
clanguml::package_diagram::model::diagram &model)
{
using namespace clanguml::package_diagram::generators::plantuml;
std::stringstream ss;
ss << generator(
dynamic_cast<clanguml::config::package_diagram &>(*config), model);
return ss.str();
}
void save_puml(const std::string &path, const std::string &puml) void save_puml(const std::string &path, const std::string &puml)
{ {
std::filesystem::path p{path}; std::filesystem::path p{path};
@@ -147,6 +173,11 @@ using namespace clanguml::test::matchers;
#include "t20001/test_case.h" #include "t20001/test_case.h"
#include "t20002/test_case.h" #include "t20002/test_case.h"
//
// Package diagram tests
//
#include "t30001/test_case.h"
// //
// Other tests (e.g. configuration file) // Other tests (e.g. configuration file)
// //

View File

@@ -24,6 +24,8 @@
#include "class_diagram/visitor/translation_unit_visitor.h" #include "class_diagram/visitor/translation_unit_visitor.h"
#include "config/config.h" #include "config/config.h"
#include "cx/compilation_database.h" #include "cx/compilation_database.h"
#include "package_diagram/generators/plantuml/package_diagram_generator.h"
#include "package_diagram/visitor/translation_unit_visitor.h"
#include "sequence_diagram/generators/plantuml/sequence_diagram_generator.h" #include "sequence_diagram/generators/plantuml/sequence_diagram_generator.h"
#include "sequence_diagram/visitor/translation_unit_visitor.h" #include "sequence_diagram/visitor/translation_unit_visitor.h"
#include "util/util.h" #include "util/util.h"
@@ -32,6 +34,7 @@
#include "catch.h" #include "catch.h"
#include <algorithm>
#include <complex> #include <complex>
#include <filesystem> #include <filesystem>
#include <string> #include <string>
@@ -168,6 +171,8 @@ struct AliasMatcher {
patterns.push_back("class \"" + name + "\" as "); patterns.push_back("class \"" + name + "\" as ");
patterns.push_back("abstract \"" + name + "\" as "); patterns.push_back("abstract \"" + name + "\" as ");
patterns.push_back("enum \"" + name + "\" as "); patterns.push_back("enum \"" + name + "\" as ");
patterns.push_back("component \"" + name + "\" as ");
patterns.push_back("component [" + name + "] as ");
for (const auto &line : puml) { for (const auto &line : puml) {
for (const auto &pattern : patterns) { for (const auto &pattern : patterns) {
@@ -396,6 +401,13 @@ ContainsMatcher IsField(std::string const &name,
return ContainsMatcher( return ContainsMatcher(
CasedString(pattern + " : " + type, caseSensitivity)); CasedString(pattern + " : " + type, caseSensitivity));
} }
ContainsMatcher IsPackage(std::string const &str,
CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes)
{
return ContainsMatcher(
CasedString("component [" + str + "]", caseSensitivity));
}
} }
} }
} }