From c1bce60656fd89fd7082d9e3ecc09d82f91ed28b Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Sat, 25 Mar 2023 12:20:35 +0100 Subject: [PATCH] Added JSON package diagram generator --- src/common/generators/generators.cc | 76 ++++++++++--- src/common/generators/generators.h | 3 +- .../json/package_diagram_generator.cc | 104 ++++++++++++++++++ .../json/package_diagram_generator.h | 67 +++++++++++ tests/t30001/test_case.h | 63 ++++++----- tests/test_cases.cc | 16 +++ 6 files changed, 285 insertions(+), 44 deletions(-) create mode 100644 src/package_diagram/generators/json/package_diagram_generator.cc create mode 100644 src/package_diagram/generators/json/package_diagram_generator.h diff --git a/src/common/generators/generators.cc b/src/common/generators/generators.cc index 02823113..a200e993 100644 --- a/src/common/generators/generators.cc +++ b/src/common/generators/generators.cc @@ -118,17 +118,39 @@ void generate_diagram(const std::string &od, const std::string &name, dynamic_cast(*diagram), translation_units, verbose); - auto path = std::filesystem::path{od} / fmt::format("{}.puml", name); - std::ofstream ofs; + for (const auto generator_type : generators) { + if (generator_type == generator_type_t::plantuml) { + auto path = + std::filesystem::path{od} / fmt::format("{}.puml", name); + std::ofstream ofs; - ofs.open(path, std::ofstream::out | std::ofstream::trunc); - ofs << clanguml::sequence_diagram::generators::plantuml::generator( - dynamic_cast(*diagram), - *model); + ofs.open(path, std::ofstream::out | std::ofstream::trunc); + ofs << clanguml::sequence_diagram::generators::plantuml:: + generator( + dynamic_cast( + *diagram), + *model); - ofs.close(); + ofs.close(); - LOG_INFO("Written {} diagram to {}", name, path.string()); + LOG_INFO("Written {} diagram to {}", name, path.string()); + } + else if (generator_type == generator_type_t::json) { + auto path = + std::filesystem::path{od} / fmt::format("{}.json", name); + std::ofstream ofs; + + ofs.open(path, std::ofstream::out | std::ofstream::trunc); + ofs << clanguml::sequence_diagram::generators::json::generator( + dynamic_cast( + *diagram), + *model); + + ofs.close(); + + LOG_INFO("Written {} diagram to {}", name, path.string()); + } + } } else if (diagram->type() == diagram_t::kPackage) { using diagram_config = package_diagram; @@ -141,16 +163,36 @@ void generate_diagram(const std::string &od, const std::string &name, dynamic_cast(*diagram), translation_units, verbose); - auto path = std::filesystem::path{od} / fmt::format("{}.puml", name); - std::ofstream ofs; - ofs.open(path, std::ofstream::out | std::ofstream::trunc); + for (const auto generator_type : generators) { + if (generator_type == generator_type_t::plantuml) { + auto path = + std::filesystem::path{od} / fmt::format("{}.puml", name); + std::ofstream ofs; + ofs.open(path, std::ofstream::out | std::ofstream::trunc); - ofs << clanguml::package_diagram::generators::plantuml::generator( - dynamic_cast(*diagram), *model); + ofs << clanguml::package_diagram::generators::plantuml:: + generator( + dynamic_cast(*diagram), *model); - ofs.close(); + ofs.close(); - LOG_INFO("Written {} diagram to {}", name, path.string()); + LOG_INFO("Written {} diagram to {}", name, path.string()); + } + else if (generator_type == generator_type_t::json) { + auto path = + std::filesystem::path{od} / fmt::format("{}.json", name); + std::ofstream ofs; + + ofs.open(path, std::ofstream::out | std::ofstream::trunc); + ofs << clanguml::package_diagram::generators::json::generator( + dynamic_cast(*diagram), + *model); + + ofs.close(); + + LOG_INFO("Written {} diagram to {}", name, path.string()); + } + } } else if (diagram->type() == diagram_t::kInclude) { using diagram_config = include_diagram; @@ -188,8 +230,8 @@ void generate_diagrams(const std::vector &diagram_names, std::vector> futs; for (const auto &[name, diagram] : config.diagrams) { - // If there are any specific diagram names provided on the command line, - // and this diagram is not in that list - skip it + // If there are any specific diagram names provided on the command + // line, and this diagram is not in that list - skip it if (!diagram_names.empty() && !util::contains(diagram_names, name)) continue; diff --git a/src/common/generators/generators.h b/src/common/generators/generators.h index 67d924ad..5d8ea709 100644 --- a/src/common/generators/generators.h +++ b/src/common/generators/generators.h @@ -24,6 +24,7 @@ #include "common/model/diagram_filter.h" #include "config/config.h" #include "include_diagram/generators/plantuml/include_diagram_generator.h" +#include "package_diagram/generators/json/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/plantuml/sequence_diagram_generator.h" @@ -144,7 +145,7 @@ std::unique_ptr generate( DiagramConfig &config, const std::vector &translation_units, bool /*verbose*/ = false) { - LOG_INFO("Generating diagram {}.puml", name); + LOG_INFO("Generating diagram {}", name); auto diagram = std::make_unique(); diagram->set_name(name); diff --git a/src/package_diagram/generators/json/package_diagram_generator.cc b/src/package_diagram/generators/json/package_diagram_generator.cc new file mode 100644 index 00000000..b1c62a51 --- /dev/null +++ b/src/package_diagram/generators/json/package_diagram_generator.cc @@ -0,0 +1,104 @@ +/** + * src/package_diagram/generators/json/package_diagram_generator.cc + * + * Copyright (c) 2021-2023 Bartek Kryza + * + * 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::json { + +generator::generator(diagram_config &config, diagram_model &model) + : common_generator{config, model} +{ +} + +void generator::generate_relationships( + const package &p, nlohmann::json &parent) const +{ + LOG_DBG("Generating relationships for package {}", p.full_name(true)); + + // Generate this packages relationship + if (m_model.should_include(relationship_t::kDependency)) { + for (const auto &r : p.relationships()) { + nlohmann::json rel = r; + rel["source"] = std::to_string(p.id()); + parent["relationships"].push_back(std::move(rel)); + } + } + + // Process it's subpackages relationships + for (const auto &subpackage : p) { + generate_relationships( + dynamic_cast(*subpackage), parent); + } +} + +void generator::generate(const package &p, nlohmann::json &parent) const +{ + LOG_DBG("Generating package {}", p.name()); + + nlohmann::json j; + j["id"] = p.id(); + j["name"] = p.name(); + j["type"] = "namespace"; + j["display_name"] = p.full_name(false); + j["is_deprecated"] = p.is_deprecated(); + if (!p.file().empty()) + j["source_location"] = + dynamic_cast(p); + if (const auto &comment = p.comment(); comment) + j["comment"] = comment.value(); + + for (const auto &subpackage : p) { + auto &pkg = dynamic_cast(*subpackage); + if (m_model.should_include(pkg)) { + generate(pkg, j); + } + } + + parent["elements"].push_back(std::move(j)); +} + +void generator::generate(std::ostream &ostr) const +{ + if (m_config.using_namespace) + json_["using_namespace"] = m_config.using_namespace().to_string(); + + json_["name"] = m_model.name(); + json_["diagram_type"] = "package"; + + json_["elements"] = std::vector{}; + json_["relationships"] = std::vector{}; + + for (const auto &p : m_model) { + auto &pkg = dynamic_cast(*p); + if (m_model.should_include(pkg)) { + generate(pkg, json_); + } + } + + // Process package relationships + for (const auto &p : m_model) { + if (m_model.should_include(dynamic_cast(*p))) + generate_relationships(dynamic_cast(*p), json_); + } + + ostr << json_; +} + +} // namespace clanguml::package_diagram::generators::json diff --git a/src/package_diagram/generators/json/package_diagram_generator.h b/src/package_diagram/generators/json/package_diagram_generator.h new file mode 100644 index 00000000..7c084166 --- /dev/null +++ b/src/package_diagram/generators/json/package_diagram_generator.h @@ -0,0 +1,67 @@ +/** + * src/package_diagram/generators/json/package_diagram_generator.h + * + * Copyright (c) 2021-2023 Bartek Kryza + * + * 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/json/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 +#include +#include +#include + +namespace clanguml { +namespace package_diagram { +namespace generators { +namespace json { + +using diagram_config = clanguml::config::package_diagram; +using diagram_model = clanguml::package_diagram::model::diagram; + +template +using common_generator = clanguml::common::generators::json::generator; + +using clanguml::common::model::access_t; +using clanguml::common::model::package; +using clanguml::common::model::relationship_t; +using namespace clanguml::util; + +class generator : public common_generator { +public: + generator(diagram_config &config, diagram_model &model); + + void generate_relationships(const package &p, nlohmann::json &parent) const; + + void generate(const package &e, nlohmann::json &parent) const; + + void generate(std::ostream &ostr) const override; + +private: + mutable nlohmann::json json_; +}; + +} // namespace json +} // namespace generators +} // namespace package_diagram +} // namespace clanguml diff --git a/tests/t30001/test_case.h b/tests/t30001/test_case.h index 9fc5ca7d..25fa9c2e 100644 --- a/tests/t30001/test_case.h +++ b/tests/t30001/test_case.h @@ -32,37 +32,48 @@ TEST_CASE("t30001", "[test-case][package]") REQUIRE(!model->should_include("clanguml::t30001::detail::C")); REQUIRE(!model->should_include("std::vector")); - auto puml = generate_package_puml(diagram, *model); - AliasMatcher _A(puml); + { + auto puml = generate_package_puml(diagram, *model); + AliasMatcher _A(puml); - REQUIRE_THAT(puml, StartsWith("@startuml")); - REQUIRE_THAT(puml, EndsWith("@enduml\n")); + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); - REQUIRE_THAT(puml, IsPackage("A")); - REQUIRE_THAT(puml, IsPackage("AAA")); - REQUIRE_THAT(puml, IsPackage("AAA")); + REQUIRE_THAT(puml, IsPackage("A")); + REQUIRE_THAT(puml, IsPackage("AAA")); + REQUIRE_THAT(puml, IsPackage("AAA")); - // TODO: Fix _A() to handle fully qualified names, right - // now it only finds the first element with unqualified - // name match - REQUIRE_THAT( - puml, HasNote(_A("AA"), "top", "This is namespace AA in namespace A")); + // TODO: Fix _A() to handle fully qualified names, right + // now it only finds the first element with unqualified + // name match + REQUIRE_THAT(puml, + HasNote(_A("AA"), "top", "This is namespace AA in namespace A")); - REQUIRE_THAT(puml, - HasLink(_A("AAA"), - fmt::format("https://github.com/bkryza/clang-uml/blob/{}/tests/" - "t30001/t30001.cc#L6", - clanguml::util::get_git_commit()), - "AAA")); + REQUIRE_THAT(puml, + HasLink(_A("AAA"), + fmt::format("https://github.com/bkryza/clang-uml/blob/{}/tests/" + "t30001/t30001.cc#L6", + clanguml::util::get_git_commit()), + "AAA")); - REQUIRE_THAT(puml, - HasLink(_A("BBB"), - fmt::format("https://github.com/bkryza/clang-uml/blob/{}/tests/" - "t30001/t30001.cc#L8", - clanguml::util::get_git_commit()), - "BBB")); + REQUIRE_THAT(puml, + HasLink(_A("BBB"), + fmt::format("https://github.com/bkryza/clang-uml/blob/{}/tests/" + "t30001/t30001.cc#L8", + clanguml::util::get_git_commit()), + "BBB")); - REQUIRE_THAT(puml, HasComment("t30001 test diagram of type package")); + REQUIRE_THAT(puml, HasComment("t30001 test diagram of type package")); - save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); + save_puml( + config.output_directory() + "/" + diagram->name + ".puml", puml); + } + + { + auto j = generate_package_json(diagram, *model); + + using namespace json; + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); + } } diff --git a/tests/test_cases.cc b/tests/test_cases.cc index 4d3769a0..265a8608 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -200,6 +200,22 @@ std::string generate_package_puml( return ss.str(); } +nlohmann::json generate_package_json( + std::shared_ptr config, + clanguml::package_diagram::model::diagram &model) +{ + using namespace clanguml::package_diagram::generators::json; + + std::stringstream ss; + + assert(config.get() != nullptr); + + ss << generator( + dynamic_cast(*config), model); + + return nlohmann::json::parse(ss.str()); +} + std::string generate_include_puml( std::shared_ptr config, clanguml::include_diagram::model::diagram &model)