Added initial support for MermaidJS package diagrams

This commit is contained in:
Bartek Kryza
2023-09-09 00:18:03 +02:00
parent 32fda88852
commit ee998e7a38
16 changed files with 360 additions and 6 deletions

View File

@@ -29,6 +29,7 @@
#include "include_diagram/generators/plantuml/include_diagram_generator.h"
#include "indicators/indicators.hpp"
#include "package_diagram/generators/json/package_diagram_generator.h"
#include "package_diagram/generators/mermaid/package_diagram_generator.h"
#include "package_diagram/generators/plantuml/package_diagram_generator.h"
#include "sequence_diagram/generators/json/sequence_diagram_generator.h"
#include "sequence_diagram/generators/mermaid/sequence_diagram_generator.h"
@@ -174,11 +175,11 @@ struct diagram_generator_t<clanguml::config::sequence_diagram,
mermaid_generator_tag> {
using type = clanguml::sequence_diagram::generators::mermaid::generator;
};
// template <>
// struct diagram_generator_t<clanguml::config::package_diagram,
// mermaid_generator_tag> {
// using type = clanguml::package_diagram::generators::mermaid::generator;
// };
template <>
struct diagram_generator_t<clanguml::config::package_diagram,
mermaid_generator_tag> {
using type = clanguml::package_diagram::generators::mermaid::generator;
};
// template <>
// struct diagram_generator_t<clanguml::config::include_diagram,
// mermaid_generator_tag> {

View File

@@ -126,7 +126,7 @@ public:
* @param ostr Output stream
* @param element Element to which the note should be attached
*/
void generate_notes(
virtual void generate_notes(
std::ostream &ostr, const model::element &element) const;
/**

View File

@@ -0,0 +1,169 @@
/**
* @file src/package_diagram/generators/mermaid/package_diagram_generator.cc
*
* Copyright (c) 2021-2023 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::mermaid {
using clanguml::common::generators::mermaid::indent;
generator::generator(diagram_config &config, diagram_model &model)
: common_generator<diagram_config, diagram_model>{config, model}
, together_group_stack_{false}
{
}
void generator::generate_relationships(
const package &p, std::ostream &ostr) const
{
LOG_DBG("Generating relationships for package {}", p.full_name(true));
// Generate this packages relationship
if (model().should_include(relationship_t::kDependency)) {
for (const auto &r : p.relationships()) {
std::stringstream relstr;
try {
auto destination = model().to_alias(r.destination());
if (!destination.empty()) {
relstr << p.alias() << " -.-> " << destination << '\n';
ostr << indent(1) << relstr.str();
}
}
catch (error::uml_alias_missing &e) {
LOG_DBG("=== Skipping dependency relation from {} to {} due "
"to: {}",
p.full_name(false), r.destination(), e.what());
}
}
}
// Process it's subpackages relationships
for (const auto &subpackage : p) {
generate_relationships(
dynamic_cast<const package &>(*subpackage), ostr);
}
}
void generator::generate(const package &p, std::ostream &ostr) const
{
LOG_DBG("Generating package {}", p.name());
together_group_stack_.enter();
const auto &uns = config().using_namespace();
// Don't generate packages from namespaces filtered out by
// using_namespace
if (!uns.starts_with({p.full_name(false)})) {
ostr << indent(1) << "subgraph " << p.alias() << "[" << p.name()
<< "]\n";
if (p.is_deprecated())
ostr << indent(1) << "%% <<deprecated>>";
}
for (const auto &subpackage : p) {
auto &pkg = dynamic_cast<package &>(*subpackage);
if (model().should_include(pkg)) {
auto together_group =
config().get_together_group(pkg.full_name(false));
if (together_group) {
together_group_stack_.group_together(
together_group.value(), &pkg);
}
else {
generate(pkg, ostr);
}
}
}
generate_groups(ostr);
if (!uns.starts_with({p.full_name(false)})) {
ostr << indent(1) << "end" << '\n';
}
generate_notes(ostr, p);
together_group_stack_.leave();
}
void generator::generate_groups(std::ostream &ostr) const
{
for (const auto &[group_name, group_elements] :
together_group_stack_.get_current_groups()) {
ostr << indent(1) << "%% together group - not rendered in MermaidJS \n";
for (auto *pkg : group_elements) {
generate(*pkg, ostr);
}
ostr << indent(1) << "%% end together group\n";
}
}
void generator::generate_notes(
std::ostream &ostr, const common::model::element &element) const
{
const auto &config =
common_generator<diagram_config, diagram_model>::config();
for (const auto &decorator : element.decorators()) {
auto note = std::dynamic_pointer_cast<decorators::note>(decorator);
if (note && note->applies_to_diagram(config.name)) {
auto note_id_str = fmt::format("N_{}", note_id_++);
ostr << indent(1) << note_id_str << "(" << note->text << ")\n";
ostr << indent(1) << note_id_str << "-.-" << element.alias()
<< '\n';
}
}
}
void generator::generate_diagram(std::ostream &ostr) const
{
ostr << "flowchart\n";
for (const auto &p : model()) {
auto &pkg = dynamic_cast<package &>(*p);
if (model().should_include(pkg)) {
auto together_group =
config().get_together_group(pkg.full_name(false));
if (together_group) {
together_group_stack_.group_together(
together_group.value(), &pkg);
}
else {
generate(pkg, ostr);
}
}
}
generate_groups(ostr);
// Process package relationships
for (const auto &p : model()) {
if (model().should_include(dynamic_cast<package &>(*p)))
generate_relationships(dynamic_cast<package &>(*p), ostr);
}
}
} // namespace clanguml::package_diagram::generators::mermaid

View File

@@ -0,0 +1,110 @@
/**
* @file src/package_diagram/generators/mermaid/package_diagram_generator.h
*
* Copyright (c) 2021-2023 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 "common/generators/mermaid/generator.h"
#include "common/generators/nested_element_stack.h"
#include "common/model/package.h"
#include "common/model/relationship.h"
#include "config/config.h"
#include "package_diagram/model/diagram.h"
#include "package_diagram/visitor/translation_unit_visitor.h"
#include "util/util.h"
#include <filesystem>
#include <fstream>
#include <iostream>
#include <sstream>
namespace clanguml {
namespace package_diagram {
namespace generators {
namespace mermaid {
using diagram_config = clanguml::config::package_diagram;
using diagram_model = clanguml::package_diagram::model::diagram;
template <typename C, typename D>
using common_generator = clanguml::common::generators::mermaid::generator<C, D>;
using clanguml::common::model::access_t;
using clanguml::common::model::package;
using clanguml::common::model::relationship_t;
using namespace clanguml::util;
/**
* @brief Package diagram MermaidJS generator
*/
class generator : public common_generator<diagram_config, diagram_model> {
public:
generator(diagram_config &config, diagram_model &model);
using common_generator<diagram_config, diagram_model>::generate;
/**
* @brief Main generator method.
*
* This method is called first and coordinates the entire diagram
* generation.
*
* @param ostr Output stream.
*/
void generate_diagram(std::ostream &ostr) const override;
/**
* @brief Generate relationships originating from package `p`
*
* @param p Diagram element
* @param parent Output stream
*/
void generate_relationships(const package &p, std::ostream &ostr) const;
/**
* @brief Generate diagram package element
*
* @param p Package diagram element
* @param parent Output stream
*/
void generate(const package &e, std::ostream &ostr) const;
/**
* @brief Generate package elements grouped using `together` MermaidJS tag
*
* @param ostr Output stream
*/
void generate_groups(std::ostream &ostr) const;
/**
* @brief Generate notes attached to packages
*
* @param ostr Output stream
* @param element Element with a note
*/
void generate_notes(std::ostream &ostr,
const common::model::element &element) const override;
private:
mutable unsigned long note_id_{0UL};
mutable common::generators::nested_element_stack<common::model::package>
together_group_stack_;
};
} // namespace mermaid
} // namespace generators
} // namespace package_diagram
} // namespace clanguml

View File

@@ -82,4 +82,10 @@ TEST_CASE("t30001", "[test-case][package]")
save_json(config.output_directory(), diagram->name + ".json", j);
}
{
auto mmd = generate_package_mermaid(diagram, *model);
save_mermaid(config.output_directory(), diagram->name + ".mmd", mmd);
}
}

View File

@@ -121,4 +121,10 @@ TEST_CASE("t30002", "[test-case][package]")
save_json(config.output_directory(), diagram->name + ".json", j);
}
{
auto mmd = generate_package_mermaid(diagram, *model);
save_mermaid(config.output_directory(), diagram->name + ".mmd", mmd);
}
}

View File

@@ -63,4 +63,10 @@ TEST_CASE("t30003", "[test-case][package]")
save_json(config.output_directory(), diagram->name + ".json", j);
}
{
auto mmd = generate_package_mermaid(diagram, *model);
save_mermaid(config.output_directory(), diagram->name + ".mmd", mmd);
}
}

View File

@@ -58,4 +58,10 @@ TEST_CASE("t30004", "[test-case][package]")
save_json(config.output_directory(), diagram->name + ".json", j);
}
{
auto mmd = generate_package_mermaid(diagram, *model);
save_mermaid(config.output_directory(), diagram->name + ".mmd", mmd);
}
}

View File

@@ -65,4 +65,10 @@ TEST_CASE("t30005", "[test-case][package]")
save_json(config.output_directory(), diagram->name + ".json", j);
}
{
auto mmd = generate_package_mermaid(diagram, *model);
save_mermaid(config.output_directory(), diagram->name + ".mmd", mmd);
}
}

View File

@@ -59,4 +59,10 @@ TEST_CASE("t30006", "[test-case][package]")
save_json(config.output_directory(), diagram->name + ".json", j);
}
{
auto mmd = generate_package_mermaid(diagram, *model);
save_mermaid(config.output_directory(), diagram->name + ".mmd", mmd);
}
}

View File

@@ -63,4 +63,10 @@ TEST_CASE("t30007", "[test-case][package]")
save_json(config.output_directory(), diagram->name + ".json", j);
}
{
auto mmd = generate_package_mermaid(diagram, *model);
save_mermaid(config.output_directory(), diagram->name + ".mmd", mmd);
}
}

View File

@@ -77,4 +77,10 @@ TEST_CASE("t30008", "[test-case][package]")
save_json(config.output_directory(), diagram->name + ".json", j);
}
{
auto mmd = generate_package_mermaid(diagram, *model);
save_mermaid(config.output_directory(), diagram->name + ".mmd", mmd);
}
}

View File

@@ -64,4 +64,10 @@ TEST_CASE("t30009", "[test-case][package]")
save_json(config.output_directory(), diagram->name + ".json", j);
}
{
auto mmd = generate_package_mermaid(diagram, *model);
save_mermaid(config.output_directory(), diagram->name + ".mmd", mmd);
}
}

View File

@@ -57,4 +57,10 @@ TEST_CASE("t30010", "[test-case][package]")
save_json(config.output_directory(), diagram->name + ".json", j);
}
{
auto mmd = generate_package_mermaid(diagram, *model);
save_mermaid(config.output_directory(), diagram->name + ".mmd", mmd);
}
}

View File

@@ -57,4 +57,10 @@ TEST_CASE("t30011", "[test-case][package]")
save_json(config.output_directory(), diagram->name + ".json", j);
}
{
auto mmd = generate_package_mermaid(diagram, *model);
save_mermaid(config.output_directory(), diagram->name + ".mmd", mmd);
}
}

View File

@@ -243,6 +243,14 @@ std::string generate_sequence_mermaid(
config, model);
}
std::string generate_package_mermaid(
std::shared_ptr<clanguml::config::diagram> config,
clanguml::package_diagram::model::diagram &model)
{
return detail::generate_diagram_mermaid<clanguml::config::package_diagram>(
config, model);
}
template <typename T>
void save_diagram(const std::filesystem::path &path, const T &diagram)
{