diff --git a/src/class_diagram/generators/json/class_diagram_generator.cc b/src/class_diagram/generators/json/class_diagram_generator.cc index f6ac941b..c900e67e 100644 --- a/src/class_diagram/generators/json/class_diagram_generator.cc +++ b/src/class_diagram/generators/json/class_diagram_generator.cc @@ -168,7 +168,11 @@ void generator::generate(const package &p, nlohmann::json &parent) const if (!uns.starts_with({p.full_name(false)})) { LOG_DBG("Generating package {}", p.name()); - package_object["type"] = "namespace"; + if (m_config.package_type() == config::package_type_t::kDirectory) + package_object["type"] = "directory"; + else + package_object["type"] = "namespace"; + package_object["name"] = p.name(); package_object["display_name"] = p.full_name(false); } diff --git a/src/class_diagram/model/diagram.cc b/src/class_diagram/model/diagram.cc index 56b37016..6e80776f 100644 --- a/src/class_diagram/model/diagram.cc +++ b/src/class_diagram/model/diagram.cc @@ -173,6 +173,15 @@ common::optional_ref diagram::get_concept( return {}; } +bool diagram::add_package_fs(std::unique_ptr &&p) +{ + LOG_DBG("Adding filesystem package: {}, {}", p->name(), p->full_name(true)); + + auto ns = p->get_namespace(); + + return add_element(ns, std::move(p)); +} + bool diagram::add_package(std::unique_ptr &&p) { LOG_DBG("Adding namespace package: {}, {}", p->name(), p->full_name(true)); @@ -182,6 +191,25 @@ bool diagram::add_package(std::unique_ptr &&p) return add_element(ns, std::move(p)); } +bool diagram::add_class_fs( + const common::model::path &p, std::unique_ptr &&c) +{ + const auto base_name = c->name(); + const auto full_name = c->full_name(false); + const auto id = c->id(); + auto &cc = *c; + + if (add_element(p, std::move(c))) { + classes_.push_back(std::ref(cc)); + return true; + } + else { + LOG_WARN("Cannot add class {} with id {} due to: {}", base_name, id); + } + + return false; +} + bool diagram::add_class(std::unique_ptr &&c) { const auto base_name = c->name(); @@ -221,7 +249,7 @@ bool diagram::add_class(std::unique_ptr &&c) } catch (const std::runtime_error &e) { LOG_WARN( - "Cannot add concept {} with id {} due to: {}", name, id, e.what()); + "Cannot add class {} with id {} due to: {}", name, id, e.what()); return false; } diff --git a/src/class_diagram/model/diagram.h b/src/class_diagram/model/diagram.h index cb0e39b5..5077905b 100644 --- a/src/class_diagram/model/diagram.h +++ b/src/class_diagram/model/diagram.h @@ -31,10 +31,15 @@ namespace clanguml::class_diagram::model { -class diagram : public clanguml::common::model::diagram, - public clanguml::common::model::nested_trait< - clanguml::common::model::element, - clanguml::common::model::namespace_> { +using common::opt_ref; +using common::model::diagram_element; +using common::model::diagram_t; + +using nested_trait_ns = + clanguml::common::model::nested_trait; + +class diagram : public common::model::diagram::diagram, public nested_trait_ns { public: diagram() = default; @@ -43,13 +48,11 @@ public: diagram &operator=(const diagram &) = delete; diagram &operator=(diagram &&) = default; - common::model::diagram_t type() const override; + diagram_t type() const override; - common::optional_ref get( - const std::string &full_name) const override; + opt_ref get(const std::string &full_name) const override; - common::optional_ref get( - clanguml::common::model::diagram_element::id_t id) const override; + opt_ref get(diagram_element::id_t id) const override; const common::reference_vector &classes() const; @@ -63,20 +66,20 @@ public: bool has_concept(const concept_ &e) const; - common::optional_ref get_class(const std::string &name) const; + opt_ref get_class(const std::string &name) const; - common::optional_ref get_class( - clanguml::common::model::diagram_element::id_t id) const; + opt_ref get_class(diagram_element::id_t id) const; - common::optional_ref get_enum(const std::string &name) const; + opt_ref get_enum(const std::string &name) const; - common::optional_ref get_enum( - clanguml::common::model::diagram_element::id_t id) const; + opt_ref get_enum(diagram_element::id_t id) const; - common::optional_ref get_concept(const std::string &name) const; + opt_ref get_concept(const std::string &name) const; - common::optional_ref get_concept( - clanguml::common::model::diagram_element::id_t id) const; + opt_ref get_concept(diagram_element::id_t id) const; + + bool add_class_fs( + const common::model::path &p, std::unique_ptr &&c); bool add_class(std::unique_ptr &&c); @@ -86,15 +89,15 @@ public: bool add_package(std::unique_ptr &&p); - std::string to_alias( - clanguml::common::model::diagram_element::id_t id) const; + bool add_package_fs(std::unique_ptr &&p); + + std::string to_alias(diagram_element::id_t id) const; void get_parents(clanguml::common::reference_set &parents) const; friend void print_diagram_tree(const diagram &d, int level); - bool has_element( - clanguml::common::model::diagram_element::id_t id) const override; + bool has_element(diagram_element::id_t id) const override; inja::json context() const override; diff --git a/src/class_diagram/visitor/translation_unit_visitor.cc b/src/class_diagram/visitor/translation_unit_visitor.cc index 66d5a526..df42aae3 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.cc +++ b/src/class_diagram/visitor/translation_unit_visitor.cc @@ -40,6 +40,9 @@ bool translation_unit_visitor::VisitNamespaceDecl(clang::NamespaceDecl *ns) { assert(ns != nullptr); + if (config().package_type() == config::package_type_t::kDirectory) + return true; + if (ns->isAnonymousNamespace() || ns->isInline()) return true; @@ -741,11 +744,11 @@ bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls) } forward_declarations_.erase(id); - if (diagram_.should_include(class_model)) { + if (diagram().should_include(class_model)) { LOG_DBG("Adding class {} with id {}", class_model.full_name(false), class_model.id()); - diagram_.add_class(std::move(c_ptr)); + add_class(std::move(c_ptr)); } else { LOG_DBG("Skipping class {} with id {}", class_model.full_name(), @@ -1320,7 +1323,7 @@ void translation_unit_visitor::process_method( relationships.emplace_back(template_specialization_ptr->id(), relationship_t::kDependency); - diagram().add_class(std::move(template_specialization_ptr)); + add_class(std::move(template_specialization_ptr)); } } } @@ -1657,7 +1660,7 @@ void translation_unit_visitor::process_function_parameter( relationships.emplace_back(template_specialization_ptr->id(), relationship_t::kDependency); - diagram().add_class(std::move(template_specialization_ptr)); + add_class(std::move(template_specialization_ptr)); } } @@ -1708,9 +1711,8 @@ void translation_unit_visitor::ensure_lambda_type_is_relative( absolute_lambda_path_end - (lambda_begin + lambda_prefix.size() - 1)); - auto relative_lambda_path = util::path_to_url(std::filesystem::relative( - absolute_lambda_path, config().relative_to()) - .string()); + auto relative_lambda_path = util::path_to_url( + config().make_path_relative(absolute_lambda_path).string()); parameter_type = fmt::format("{}(lambda at {}{}", parameter_type.substr(0, lambda_begin), relative_lambda_path, @@ -1972,7 +1974,7 @@ void translation_unit_visitor::process_field( // Add the template instantiation object to the diagram if it // matches the include pattern if (add_template_instantiation_to_diagram) - diagram().add_class(std::move(template_specialization_ptr)); + add_class(std::move(template_specialization_ptr)); } } @@ -2004,7 +2006,7 @@ void translation_unit_visitor::add_incomplete_forward_declarations() { for (auto &[id, c] : forward_declarations_) { if (diagram().should_include(c->full_name(false))) { - diagram().add_class(std::move(c)); + add_class(std::move(c)); } } forward_declarations_.clear(); @@ -2092,4 +2094,35 @@ bool translation_unit_visitor::has_processed_template_class( return util::contains(processed_template_qualified_names_, qualified_name); } +void translation_unit_visitor::add_class(std::unique_ptr &&c) +{ + if ((config().generate_packages() && + config().package_type() == config::package_type_t::kDirectory)) { + assert(!c->file().empty()); + + const auto file = config().make_path_relative(c->file()); + + common::model::path p{file, common::model::path_type::kFilesystem}; + p.pop_back(); + + // Make sure all parent directories are already packages in the model + for (auto it = p.begin(); it != p.end(); it++) { + auto pkg = std::make_unique( + config().using_namespace()); + pkg->set_name(*it); + auto ns = common::model::path(p.begin(), it); + // ns.pop_back(); + pkg->set_namespace(ns); + pkg->set_id(common::to_id(pkg->full_name(false))); + + diagram().add_package_fs(std::move(pkg)); + } + + diagram().add_class_fs(p, std::move(c)); + } + else { + diagram().add_class(std::move(c)); + } +} + } // namespace clanguml::class_diagram::visitor diff --git a/src/class_diagram/visitor/translation_unit_visitor.h b/src/class_diagram/visitor/translation_unit_visitor.h index ae12cf50..3458db52 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.h +++ b/src/class_diagram/visitor/translation_unit_visitor.h @@ -119,6 +119,8 @@ public: void finalize(); private: + void add_class(std::unique_ptr &&c); + bool should_include(const clang::NamedDecl *decl); std::unique_ptr diff --git a/src/common/model/diagram.h b/src/common/model/diagram.h index b36a7ead..ab58529f 100644 --- a/src/common/model/diagram.h +++ b/src/common/model/diagram.h @@ -39,7 +39,7 @@ public: virtual diagram_t type() const = 0; - virtual common::optional_ref get( + virtual opt_ref get( const std::string &full_name) const = 0; virtual common::optional_ref get( diff --git a/src/common/model/diagram_filter.cc b/src/common/model/diagram_filter.cc index d3b1db31..3ab7080f 100644 --- a/src/common/model/diagram_filter.cc +++ b/src/common/model/diagram_filter.cc @@ -430,8 +430,8 @@ paths_filter::paths_filter(filter_t type, const std::filesystem::path &root, absolute_path = path; try { - absolute_path = - std::filesystem::canonical(absolute_path.lexically_normal()); + absolute_path = absolute(absolute_path); + absolute_path = canonical(absolute_path.lexically_normal()); } catch (std::filesystem::filesystem_error &e) { LOG_WARN("Cannot add non-existent path {} to paths filter", @@ -539,7 +539,7 @@ void diagram_filter::init_filters(const config::diagram &c) filter_t::kInclusive, c.include().access)); add_inclusive_filter(std::make_unique( - filter_t::kInclusive, c.relative_to(), c.include().paths)); + filter_t::kInclusive, c.root_directory(), c.include().paths)); // Include any of these matches even if one them does not match std::vector> element_filters; @@ -623,7 +623,7 @@ void diagram_filter::init_filters(const config::diagram &c) filter_t::kExclusive, c.exclude().namespaces)); add_exclusive_filter(std::make_unique( - filter_t::kExclusive, c.relative_to(), c.exclude().paths)); + filter_t::kExclusive, c.root_directory(), c.exclude().paths)); add_exclusive_filter(std::make_unique( filter_t::kExclusive, c.exclude().elements)); diff --git a/src/common/model/namespace.h b/src/common/model/namespace.h index 0f2cf8ca..f5786c5e 100644 --- a/src/common/model/namespace.h +++ b/src/common/model/namespace.h @@ -29,7 +29,7 @@ struct ns_path_separator { static constexpr std::string_view value = "::"; }; -using namespace_ = path; +using namespace_ = path; } diff --git a/src/common/model/package.h b/src/common/model/package.h index a4d13837..c2163a2e 100644 --- a/src/common/model/package.h +++ b/src/common/model/package.h @@ -19,6 +19,7 @@ #include "common/model/element.h" #include "common/model/nested_trait.h" +#include "common/model/path.h" #include "common/model/stylable_element.h" #include "common/types.h" #include "util/util.h" @@ -33,9 +34,9 @@ namespace clanguml::common::model { class package : public element, public stylable_element, - public nested_trait { + public nested_trait { public: - package(const common::model::namespace_ &using_namespace); + package(const common::model::path &using_namespace); package(const package &) = delete; package(package &&) = default; diff --git a/src/common/model/path.h b/src/common/model/path.h index 5bf0d030..577b24f0 100644 --- a/src/common/model/path.h +++ b/src/common/model/path.h @@ -25,41 +25,84 @@ namespace clanguml::common::model { -template class path { +enum class path_type { kNamespace, kFilesystem }; + +class path { + + const char *separator() const + { + switch (path_type_) { + case path_type::kNamespace: + return "::"; + case path_type::kFilesystem: +#ifdef _WIN32 + return "\\"; +#else + return "/"; +#endif + } + + return "::"; + } + public: using container_type = std::vector; - path() = default; + path(path_type pt = path_type::kNamespace) + : path_type_{pt} + { + } - explicit path(const std::string &ns) + path(const std::string &ns, path_type pt = path_type::kNamespace) + : path_type_{pt} { if (ns.empty()) return; - path_ = util::split(ns, Sep::value); + path_ = util::split(ns, separator()); } + virtual ~path() = default; + path(container_type::const_iterator begin, - container_type::const_iterator end) + container_type::const_iterator end, + path_type pt = path_type::kNamespace) + : path(pt) { + if (begin == end) + return; + std::copy(begin, end, std::back_inserter(path_)); } path(const path &right) - : path_{right.path_} + : path_type_{right.path_type_} + , path_{right.path_} { } - path &operator=(const path &right) = default; + path &operator=(const path &right) + { + if (path_type_ != right.path_type_) + throw std::runtime_error(""); + + path_type_ = right.path_type_; + path_ = right.path_; + + return *this; + } path(path &&right) noexcept = default; path &operator=(path &&right) noexcept = default; - path(std::initializer_list ns) + path(std::initializer_list ns, + path_type pt = path_type::kNamespace) + : path(pt) { - if ((ns.size() == 1) && util::contains(*ns.begin(), Sep::value)) { - path_ = util::split(*ns.begin(), Sep::value); + if ((ns.size() == 1) && + util::contains(*ns.begin(), std::string{separator()})) { + path_ = util::split(*ns.begin(), separator()); } else if ((ns.size() == 1) && ns.begin()->empty()) { } @@ -67,10 +110,13 @@ public: path_ = ns; } - explicit path(const std::vector &ns) + explicit path(const std::vector &ns, + path_type pt = path_type::kNamespace) + : path(pt) { - if ((ns.size() == 1) && util::contains(*ns.begin(), Sep::value)) { - path_ = util::split(*ns.begin(), Sep::value); + if ((ns.size() == 1) && + util::contains(*ns.begin(), std::string{separator()})) { + path_ = util::split(*ns.begin(), separator()); } else if ((ns.size() == 1) && ns.begin()->empty()) { } @@ -78,19 +124,19 @@ public: path_ = ns; } - friend bool operator==(const path &left, const path &right) + friend bool operator==(const path &left, const path &right) { return left.path_ == right.path_; } - friend bool operator<(const path &left, const path &right) + friend bool operator<(const path &left, const path &right) { - return std::hash>{}(left) < std::hash>{}(right); + return left.to_string() < right.to_string(); } std::string to_string() const { - return fmt::format("{}", fmt::join(path_, Sep::value)); + return fmt::format("{}", fmt::join(path_, std::string{separator()})); } bool is_empty() const { return path_.empty(); } @@ -190,7 +236,7 @@ public: return name; auto res = name; - auto ns_prefix = to_string() + std::string{Sep::value}; + auto ns_prefix = to_string() + std::string{separator()}; auto it = res.find(ns_prefix); while (it != std::string::npos) { @@ -220,7 +266,10 @@ public: path::container_type::const_iterator begin() const { return path_.begin(); } path::container_type::const_iterator end() const { return path_.end(); } + path_type type() const { return path_type_; } + private: + path_type path_type_; container_type path_; }; diff --git a/src/common/model/source_file.h b/src/common/model/source_file.h index 4b802d05..11401239 100644 --- a/src/common/model/source_file.h +++ b/src/common/model/source_file.h @@ -46,7 +46,7 @@ struct fs_path_sep { #endif }; -using filesystem_path = common::model::path; +using filesystem_path = common::model::path; class source_file : public common::model::diagram_element, @@ -60,7 +60,7 @@ public: { auto preferred = p; preferred.make_preferred(); - set_path({preferred.parent_path().string()}); + set_path({preferred.parent_path().string(), path_type::kFilesystem}); set_name(preferred.filename().string()); is_absolute_ = preferred.is_absolute(); set_id(common::to_id(preferred)); @@ -126,7 +126,7 @@ public: } private: - filesystem_path path_; + filesystem_path path_{path_type::kFilesystem}; source_file_t type_{source_file_t::kDirectory}; bool is_absolute_{false}; }; @@ -134,21 +134,24 @@ private: namespace std { -template <> struct hash { - std::size_t operator()( - const clanguml::common::model::filesystem_path &key) const - { - using clanguml::common::model::path; +/* + template <> struct hash { + std::size_t operator()( + const clanguml::common::model::filesystem_path &key) const + { + using clanguml::common::model::path; - std::size_t seed = key.size(); - for (const auto &ns : key) { - seed ^= - std::hash{}(ns) + clanguml::util::hash_seed(seed); - } + std::size_t seed = key.size(); + for (const auto &ns : key) { + seed ^= + std::hash{}(ns) + + clanguml::util::hash_seed(seed); + } - return seed; - } -}; + return seed; + } + }; +*/ } // namespace std diff --git a/src/common/types.h b/src/common/types.h index 65a8d980..749812f9 100644 --- a/src/common/types.h +++ b/src/common/types.h @@ -129,6 +129,8 @@ private: T *value_{nullptr}; }; +template using opt_ref = optional_ref; + template using reference_vector = std::vector>; diff --git a/src/config/config.cc b/src/config/config.cc index c2cd7a8e..33c72478 100644 --- a/src/config/config.cc +++ b/src/config/config.cc @@ -107,6 +107,8 @@ void inheritable_diagram_options::inherit( exclude.override(parent.exclude); puml.override(parent.puml); generate_method_arguments.override(parent.generate_method_arguments); + generate_packages.override(parent.generate_packages); + package_type.override(parent.package_type); generate_links.override(parent.generate_links); generate_system_headers.override(parent.generate_system_headers); git.override(parent.git); @@ -136,14 +138,20 @@ std::vector diagram::get_translation_units() const { std::vector translation_units{}; + LOG_DBG("Looking for translation units in {}", + std::filesystem::current_path().string()); + for (const auto &g : glob()) { std::string glob_path = - fmt::format("{}/{}", relative_to().string(), g.c_str()); + fmt::format("{}/{}", root_directory().string(), g.c_str()); + + LOG_DBG("Searching glob path {}", glob_path); auto matches = glob::glob(glob_path, true, false); for (const auto &match : matches) { - const auto path = std::filesystem::canonical(relative_to() / match); + const auto path = + std::filesystem::canonical(root_directory() / match); translation_units.emplace_back(path.string()); } } @@ -151,6 +159,17 @@ std::vector diagram::get_translation_units() const return translation_units; } +std::filesystem::path diagram::root_directory() const +{ + return canonical(absolute(base_directory() / relative_to())); +} + +std::filesystem::path diagram::make_path_relative( + const std::filesystem::path &p) const +{ + return relative(p, root_directory()).lexically_normal().string(); +} + std::optional diagram::get_together_group( const std::string &full_name) const { diff --git a/src/config/config.h b/src/config/config.h index 746332be..e728a66c 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -37,6 +37,8 @@ namespace config { enum class method_arguments { full, abbreviated, none }; +enum class package_type_t { kNamespace, kDirectory }; + std::string to_string(method_arguments ma); enum class comment_parser_t { plain, clang }; @@ -162,10 +164,17 @@ struct inheritable_diagram_options { option generate_method_arguments{ "generate_method_arguments", method_arguments::full}; option generate_packages{"generate_packages", false}; + option package_type{ + "package_type", package_type_t::kNamespace}; option generate_links{"generate_links"}; option git{"git"}; option layout{"layout"}; + // This is the absolute filesystem path to the directory containing + // the current .clang-uml config file - it is set automatically option base_directory{"__parent_path"}; + // This is the relative path with respect to the `base_directory`, + // against which all matches are made, if not provided it defaults to the + // `base_directory` option relative_to{"relative_to"}; option generate_system_headers{"generate_system_headers", false}; option relationship_hints{"relationship_hints"}; @@ -190,6 +199,11 @@ struct diagram : public inheritable_diagram_options { std::vector get_translation_units() const; + std::filesystem::path make_path_relative( + const std::filesystem::path &p) const; + + std::filesystem::path root_directory() const; + std::optional get_together_group( const std::string &full_name) const; diff --git a/src/config/yaml_decoders.cc b/src/config/yaml_decoders.cc index fee09d3d..0ec9f095 100644 --- a/src/config/yaml_decoders.cc +++ b/src/config/yaml_decoders.cc @@ -34,6 +34,7 @@ using clanguml::config::layout_hint; using clanguml::config::location_t; using clanguml::config::method_arguments; using clanguml::config::package_diagram; +using clanguml::config::package_type_t; using clanguml::config::plantuml; using clanguml::config::relationship_hint_t; using clanguml::config::sequence_diagram; @@ -88,6 +89,22 @@ void get_option( } } +template <> +void get_option( + const Node &node, clanguml::config::option &option) +{ + if (node[option.name]) { + const auto &val = node[option.name].as(); + if (val == "namespace") + option.set(package_type_t::kNamespace); + else if (val == "directory") + option.set(package_type_t::kDirectory); + else + throw std::runtime_error( + "Invalid generate_method_arguments value: " + val); + } +} + template <> void get_option(const Node &node, clanguml::config::option &option) @@ -403,6 +420,7 @@ template <> struct convert { get_option(node, rhs.include_relations_also_as_members); get_option(node, rhs.generate_method_arguments); get_option(node, rhs.generate_packages); + get_option(node, rhs.package_type); get_option(node, rhs.relationship_hints); get_option(node, rhs.type_aliases); get_option(node, rhs.relative_to); @@ -597,6 +615,7 @@ template <> struct convert { get_option(node, rhs.puml); get_option(node, rhs.generate_method_arguments); get_option(node, rhs.generate_packages); + get_option(node, rhs.package_type); get_option(node, rhs.generate_links); get_option(node, rhs.generate_system_headers); get_option(node, rhs.git); diff --git a/src/include_diagram/model/diagram.cc b/src/include_diagram/model/diagram.cc index 40b5e38a..cd2e71b5 100644 --- a/src/include_diagram/model/diagram.cc +++ b/src/include_diagram/model/diagram.cc @@ -60,7 +60,8 @@ void diagram::add_file(std::unique_ptr &&f) if (!f->path().is_empty()) { // If the parent path is not empty, ensure relative parent directories // of this source_file are in the diagram - common::model::filesystem_path parent_path_so_far; + common::model::filesystem_path parent_path_so_far{ + common::model::path_type::kFilesystem}; for (const auto &directory : f->path()) { auto source_file_path = parent_path_so_far | directory; if (parent_path_so_far.is_empty()) @@ -83,6 +84,8 @@ void diagram::add_file(std::unique_ptr &&f) } } + assert(p.type() == common::model::path_type::kFilesystem); + add_element(p, std::move(f)); } diff --git a/src/sequence_diagram/generators/json/sequence_diagram_generator.cc b/src/sequence_diagram/generators/json/sequence_diagram_generator.cc index 46e75723..2f89cca4 100644 --- a/src/sequence_diagram/generators/json/sequence_diagram_generator.cc +++ b/src/sequence_diagram/generators/json/sequence_diagram_generator.cc @@ -550,8 +550,7 @@ common::id_t generator::generate_participant( nlohmann::json j = function_participant; j["name"] = util::path_to_url( - std::filesystem::relative(function_participant.file(), - std::filesystem::canonical(m_config.relative_to()).string())); + m_config.make_path_relative(function_participant.file()).string()); participant_id = common::to_id(function_participant.file_relative()); diff --git a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc index e1284905..82a40fc9 100644 --- a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc +++ b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc @@ -329,11 +329,8 @@ void generator::generate_participant( if (is_participant_generated(file_id)) return; - const auto &relative_to = - std::filesystem::canonical(m_config.relative_to()); - auto participant_name = util::path_to_url(std::filesystem::relative( - std::filesystem::path{file_path}, relative_to) + std::filesystem::path{file_path}, m_config.root_directory()) .string()); ostr << "participant \"" << render_name(participant_name) << "\" as " diff --git a/tests/t00061/.clang-uml b/tests/t00061/.clang-uml index 9643fb5c..aa1a3ec6 100644 --- a/tests/t00061/.clang-uml +++ b/tests/t00061/.clang-uml @@ -3,7 +3,7 @@ output_directory: puml diagrams: t00061_class: type: class - relative_to: ../../tests/t00061 + relative_to: ../../../tests/t00061 glob: - t00061.cc include: diff --git a/tests/t00065/.clang-uml b/tests/t00065/.clang-uml new file mode 100644 index 00000000..4c7143fb --- /dev/null +++ b/tests/t00065/.clang-uml @@ -0,0 +1,15 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t00065_class: + type: class + glob: + - t00065.cc + relative_to: ../../../tests/t00065 + generate_packages: true + package_type: directory + include: + namespaces: + - clanguml::t00065 + using_namespace: + - clanguml::t00065 \ No newline at end of file diff --git a/tests/t00065/module1/module1.h b/tests/t00065/module1/module1.h new file mode 100644 index 00000000..ee61d62f --- /dev/null +++ b/tests/t00065/module1/module1.h @@ -0,0 +1,11 @@ +#include "submodule1a/submodule1a.h" + +#pragma once + +namespace clanguml { +namespace t00065 { +struct A { + detail::AImpl *pimpl; +}; +} +} \ No newline at end of file diff --git a/tests/t00065/module1/submodule1a/submodule1a.h b/tests/t00065/module1/submodule1a/submodule1a.h new file mode 100644 index 00000000..41bad1bb --- /dev/null +++ b/tests/t00065/module1/submodule1a/submodule1a.h @@ -0,0 +1,9 @@ +#pragma once + +namespace clanguml { +namespace t00065 { +namespace detail { +struct AImpl { }; +} +} +} \ No newline at end of file diff --git a/tests/t00065/module2/module2.h b/tests/t00065/module2/module2.h new file mode 100644 index 00000000..6e90e697 --- /dev/null +++ b/tests/t00065/module2/module2.h @@ -0,0 +1,7 @@ +#pragma once + +namespace clanguml { +namespace t00065 { +struct B { }; +} +} \ No newline at end of file diff --git a/tests/t00065/t00065.cc b/tests/t00065/t00065.cc new file mode 100644 index 00000000..667316d1 --- /dev/null +++ b/tests/t00065/t00065.cc @@ -0,0 +1,10 @@ +#include "module1/module1.h" +#include "module2/module2.h" + +namespace clanguml { +namespace t00065 { +struct R { + A *a; +}; +} +} \ No newline at end of file diff --git a/tests/t00065/test_case.h b/tests/t00065/test_case.h new file mode 100644 index 00000000..a90d050e --- /dev/null +++ b/tests/t00065/test_case.h @@ -0,0 +1,83 @@ +/** + * tests/t00065/test_case.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. + */ + +TEST_CASE("t00065", "[test-case][class]") +{ + auto [config, db] = load_config("t00065"); + + auto diagram = config.diagrams["t00065_class"]; + + REQUIRE(diagram->name == "t00065_class"); + + auto model = generate_class_diagram(*db, diagram); + + REQUIRE(model->name() == "t00065_class"); + + { + auto puml = generate_class_puml(diagram, *model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + // Check if all classes exist + // REQUIRE_THAT(puml, IsClass(_A("AAA"))); + + // Check if class templates exist + // REQUIRE_THAT(puml, IsClassTemplate("A", "T,P,CMP,int N")); + + // Check concepts + // REQUIRE_THAT(puml, IsConcept(_A("AConcept"))); + // REQUIRE_THAT(puml, + // IsConceptRequirement( + // _A("AConcept"), "sizeof (T) > sizeof (P)")); + + // Check if all enums exist + // REQUIRE_THAT(puml, IsEnum(_A("Lights"))); + + // Check if all inner classes exist + // REQUIRE_THAT(puml, IsInnerClass(_A("A"), _A("AA"))); + + // Check if all inheritance relationships exist + // REQUIRE_THAT(puml, IsBaseClass(_A("Base"), _A("Child"))); + + // Check if all methods exist + // REQUIRE_THAT(puml, (IsMethod("foo"))); + + // Check if all fields exist + // REQUIRE_THAT(puml, (IsField("private_member", "int"))); + + // Check if all relationships exist + // REQUIRE_THAT(puml, IsAssociation(_A("D"), _A("A"), "-as")); + // REQUIRE_THAT(puml, IsDependency(_A("R"), _A("B"))); + // REQUIRE_THAT(puml, IsAggregation(_A("R"), _A("D"), "-ag")); + // REQUIRE_THAT(puml, IsComposition(_A("R"), _A("D"), "-ac")); + // REQUIRE_THAT(puml, IsInstantiation(_A("ABCD::F"), _A("F"))); + + save_puml( + config.output_directory() + "/" + diagram->name + ".puml", puml); + } + + { + auto j = generate_class_json(diagram, *model); + + using namespace json; + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); + } +} \ No newline at end of file diff --git a/tests/t20017/.clang-uml b/tests/t20017/.clang-uml index 860e217d..0d9947d3 100644 --- a/tests/t20017/.clang-uml +++ b/tests/t20017/.clang-uml @@ -4,9 +4,9 @@ diagrams: t20017_sequence: type: sequence combine_free_functions_into_file_participants: true - relative_to: ../../tests/t20017 + relative_to: ../../../tests/t20017 glob: - - ../../tests/t20017/t20017.cc + - t20017.cc include: namespaces: - clanguml::t20017 diff --git a/tests/test_cases.cc b/tests/test_cases.cc index 6313f195..98f03746 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -301,6 +301,7 @@ using namespace clanguml::test::matchers; #include "t00062/test_case.h" #include "t00063/test_case.h" #include "t00064/test_case.h" +#include "t00065/test_case.h" /// /// Sequence diagram tests