diff --git a/README.md b/README.md index 51bae82b..1144e315 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,9 @@ Main features supported so far include: * **Package diagram generation** * Generation of package diagram based on C++ namespaces * Interactive links to online code to packages - +* **Include graph diagram generation** + * Show include graph for selected files + To see what `clang-uml` can do so far, checkout the diagrams generated for unit test cases [here](./docs/test_cases.md). ## Installation diff --git a/src/common/model/diagram.cc b/src/common/model/diagram.cc index 7330236c..4f29e739 100644 --- a/src/common/model/diagram.cc +++ b/src/common/model/diagram.cc @@ -85,4 +85,12 @@ bool diagram::should_include( return filter_->should_include(ns, name); } +bool diagram::should_include(const common::model::source_file &f) const +{ + if (filter_.get() == nullptr) + return true; + + return filter_->should_include(f); +} + } \ No newline at end of file diff --git a/src/common/model/diagram.h b/src/common/model/diagram.h index dbcd9b52..579c2c1a 100644 --- a/src/common/model/diagram.h +++ b/src/common/model/diagram.h @@ -20,6 +20,7 @@ #include "diagram_element.h" #include "enums.h" #include "namespace.h" +#include "source_file.h" #include @@ -58,9 +59,11 @@ public: // TODO: refactor to a template method bool should_include(const element &e) const; bool should_include(const std::string &e) const; + bool should_include(const source_file &path) const; bool should_include(const relationship r) const; bool should_include(const relationship_t r) const; bool should_include(const access_t s) const; + bool should_include(const namespace_ &ns, const std::string &name) const; private: diff --git a/src/common/model/diagram_filter.cc b/src/common/model/diagram_filter.cc index 0d3d0d50..000a9cbb 100644 --- a/src/common/model/diagram_filter.cc +++ b/src/common/model/diagram_filter.cc @@ -49,6 +49,12 @@ tvl::value_t filter_visitor::match( return {}; } +tvl::value_t filter_visitor::match( + const diagram &d, const common::model::source_file &f) const +{ + return {}; +} + bool filter_visitor::is_inclusive() const { return type_ == filter_t::kInclusive; @@ -254,6 +260,40 @@ tvl::value_t context_filter::match(const diagram &d, const element &e) const }); } +paths_filter::paths_filter(filter_t type, const std::filesystem::path &root, + std::vector p) + : filter_visitor{type} + , root_{root} +{ + for (auto &&path : p) { + std::filesystem::path absolute_path; + + if (path.is_relative()) + absolute_path = root / path; + + absolute_path = absolute_path.lexically_normal(); + + paths_.emplace_back(std::move(absolute_path)); + } +} + +tvl::value_t paths_filter::match( + const diagram &d, const common::model::source_file &p) const +{ + + if (paths_.empty()) { + return {}; + } + + auto pp = p.fs_path(root_); + for (const auto &path : paths_) { + if (util::starts_with(pp, path)) + return true; + } + + return false; +} + diagram_filter::diagram_filter( const common::model::diagram &d, const config::diagram &c) : diagram_{d} @@ -295,6 +335,8 @@ void diagram_filter::init_filters(const config::diagram &c) filter_t::kInclusive, c.include().relationships)); inclusive_.emplace_back(std::make_unique( filter_t::kInclusive, c.include().access)); + inclusive_.emplace_back(std::make_unique( + filter_t::kInclusive, c.base_directory(), c.include().paths)); // Include any of these matches even if one them does not match std::vector> element_filters; @@ -305,6 +347,7 @@ void diagram_filter::init_filters(const config::diagram &c) element_filters.emplace_back(std::make_unique( filter_t::kInclusive, c.include().context)); + inclusive_.emplace_back(std::make_unique( filter_t::kInclusive, std::move(element_filters))); } @@ -323,6 +366,8 @@ void diagram_filter::init_filters(const config::diagram &c) filter_t::kExclusive, c.exclude().subclasses)); exclusive_.emplace_back(std::make_unique( filter_t::kExclusive, c.exclude().context)); + exclusive_.emplace_back(std::make_unique( + filter_t::kInclusive, c.base_directory(), c.exclude().paths)); } } diff --git a/src/common/model/diagram_filter.h b/src/common/model/diagram_filter.h index dfbc8886..b3df613e 100644 --- a/src/common/model/diagram_filter.h +++ b/src/common/model/diagram_filter.h @@ -25,8 +25,11 @@ #include "config/config.h" #include "cx/util.h" #include "diagram.h" +#include "source_file.h" #include "tvl.h" +#include + namespace clanguml::common::model { enum filter_t { kInclusive, kExclusive }; @@ -47,6 +50,9 @@ public: virtual tvl::value_t match( const diagram &d, const common::model::namespace_ &ns) const; + virtual tvl::value_t match( + const diagram &d, const common::model::source_file &f) const; + bool is_inclusive() const; bool is_exclusive() const; @@ -125,6 +131,18 @@ private: std::vector context_; }; +struct paths_filter : public filter_visitor { + paths_filter(filter_t type, const std::filesystem::path &root, + std::vector p); + + tvl::value_t match(const diagram &d, + const common::model::source_file &r) const override; + +private: + std::vector paths_; + std::filesystem::path root_; +}; + class diagram_filter { public: diagram_filter(const common::model::diagram &d, const config::diagram &c); @@ -144,7 +162,9 @@ public: return false; auto inc = tvl::all_of(inclusive_.begin(), inclusive_.end(), - [this, &e](const auto &in) { return in->match(diagram_, e); }); + [this, &e](const auto &in) { + return in->match(diagram_, e); + }); if (tvl::is_undefined(inc) || tvl::is_true(inc)) return true; diff --git a/src/common/model/nested_trait.h b/src/common/model/nested_trait.h index d79d04b3..8f083f39 100644 --- a/src/common/model/nested_trait.h +++ b/src/common/model/nested_trait.h @@ -52,7 +52,8 @@ public: } template - void add_element(const Path &path, std::unique_ptr p) + void add_element( + const Path &path, std::unique_ptr p) { assert(p); diff --git a/src/include_diagram/model/source_file.cc b/src/common/model/source_file.cc similarity index 93% rename from src/include_diagram/model/source_file.cc rename to src/common/model/source_file.cc index 3b596231..0a806652 100644 --- a/src/include_diagram/model/source_file.cc +++ b/src/common/model/source_file.cc @@ -1,5 +1,5 @@ /** - * src/include_diagram/model/source_file.cc + * src/common/model/source_file.cc * * Copyright (c) 2021-2022 Bartek Kryza * diff --git a/src/include_diagram/model/source_file.h b/src/common/model/source_file.h similarity index 69% rename from src/include_diagram/model/source_file.h rename to src/common/model/source_file.h index 0e16835e..99a2f1c7 100644 --- a/src/include_diagram/model/source_file.h +++ b/src/common/model/source_file.h @@ -1,5 +1,5 @@ /** - * src/include_diagram/model/source_file.h + * src/common/model/source_file.h * * Copyright (c) 2021-2022 Bartek Kryza * @@ -30,9 +30,9 @@ #include #include -namespace clanguml::include_diagram::model { +namespace clanguml::common::model { -enum class source_file_type { kDirectory, kHeader, kImplementation }; +enum class source_file_t { kDirectory, kHeader, kImplementation }; struct fs_path_sep { static constexpr std::string_view value = "/"; @@ -43,13 +43,19 @@ using filesystem_path = common::model::path; class source_file : public common::model::diagram_element, public common::model::stylable_element, - public common::model::nested_trait { public: source_file() = default; void set_path(const filesystem_path &p) { path_ = p; } + void set_absolute() { is_absolute_ = true; } + + void set_type(source_file_t type) { type_ = type; } + + source_file_t type() const { return type_; } + source_file(const source_file &) = delete; source_file(source_file &&) = default; source_file &operator=(const source_file &) = delete; @@ -62,23 +68,41 @@ public: return (path_ | name()).to_string(); } - void add_file(std::unique_ptr &&f) + void add_file(std::unique_ptr &&f) { LOG_DBG("Adding source file: {}, {}", f->name(), f->full_name(true)); add_element(f->path(), std::move(f)); } + std::filesystem::path fs_path(const std::filesystem::path &base = {}) const + { + std::filesystem::path res; + + for (const auto &pe : path_) { + res /= pe; + } + + if (is_absolute_) + res = "/" / res; + else + res = base / res; + + return res.lexically_normal(); + } + private: filesystem_path path_; + source_file_t type_{source_file_t::kDirectory}; + bool is_absolute_{false}; }; } namespace std { -template <> struct hash { +template <> struct hash { std::size_t operator()( - const clanguml::include_diagram::model::filesystem_path &key) const + const clanguml::common::model::filesystem_path &key) const { using clanguml::common::model::path; diff --git a/src/config/config.cc b/src/config/config.cc index 3138cc00..6764d069 100644 --- a/src/config/config.cc +++ b/src/config/config.cc @@ -15,7 +15,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + #include "config.h" + #include namespace clanguml { @@ -28,12 +30,13 @@ config load(const std::string &config_file) // Store the parent path of the config_file to properly resolve // the include files paths - auto config_file_path = std::filesystem::path{config_file}; + auto config_file_path = + std::filesystem::absolute(std::filesystem::path{config_file}); doc.force_insert( "__parent_path", config_file_path.parent_path().string()); // If the current directory is also a git repository, - // load some config values which can be included in the + // load some config values, which can be included in the // generated diagrams if (util::is_git_repository() && !doc["git"]) { YAML::Node git_config{YAML::NodeType::Map}; @@ -108,6 +111,8 @@ void inheritable_diagram_options::inherit( generate_method_arguments.override(parent.generate_method_arguments); generate_links.override(parent.generate_links); git.override(parent.git); + base_directory.override(parent.base_directory); + relative_to.override(parent.relative_to); } diagram_type class_diagram::type() const { return diagram_type::class_diagram; } @@ -245,6 +250,21 @@ std::shared_ptr parse_diagram_config(const Node &d) return {}; } +// +// config std::filesystem::path decoder +// +template <> struct convert { + static bool decode(const Node &node, std::filesystem::path &rhs) + { + if (!node.IsScalar()) + return false; + + rhs = std::filesystem::path{node.as()}; + + return true; + } +}; + // // config access_t decoder // @@ -391,6 +411,9 @@ template <> struct convert { if (node["context"]) rhs.context = node["context"].as(); + if (node["paths"]) + rhs.paths = node["paths"].as(); + return true; } }; @@ -505,6 +528,16 @@ template <> struct convert { return false; get_option(node, rhs.layout); + get_option(node, rhs.relative_to); + + // Convert the path in relative_to to an absolute path, with respect + // to the directory where the `.clang-uml` configuration file is located + if (rhs.relative_to) { + auto absolute_relative_to = + std::filesystem::path{node["__parent_path"].as()} / + rhs.relative_to(); + rhs.relative_to.set(absolute_relative_to.lexically_normal()); + } return true; } @@ -558,25 +591,29 @@ template <> struct convert { get_option(node, rhs.generate_packages); get_option(node, rhs.generate_links); get_option(node, rhs.git); + rhs.base_directory.set(node["__parent_path"].as()); + get_option(node, rhs.relative_to); auto diagrams = node["diagrams"]; assert(diagrams.Type() == NodeType::Map); - for (const auto &d : diagrams) { + for (auto d : diagrams) { auto name = d.first.as(); std::shared_ptr diagram_config{}; + auto parent_path = node["__parent_path"].as(); if (has_key(d.second, "include!")) { - auto parent_path = node["__parent_path"].as(); auto include_path = std::filesystem::path{parent_path}; include_path /= d.second["include!"].as(); YAML::Node node = YAML::LoadFile(include_path.string()); + node.force_insert("__parent_path", parent_path); diagram_config = parse_diagram_config(node); } else { + d.second.force_insert("__parent_path", parent_path); diagram_config = parse_diagram_config(d.second); } @@ -585,6 +622,9 @@ template <> struct convert { diagram_config->inherit(rhs); rhs.diagrams[name] = diagram_config; } + else { + return false; + } } return true; diff --git a/src/config/config.h b/src/config/config.h index c598d774..ce744e86 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -71,6 +71,8 @@ struct filter { std::vector subclasses; std::vector context; + + std::vector paths; }; enum class hint_t { up, down, left, right }; @@ -110,6 +112,8 @@ struct inheritable_diagram_options { option generate_packages{"generate_packages", false}; option generate_links{"generate_links"}; option git{"git"}; + option base_directory{"__parent_path"}; + option relative_to{"relative_to"}; void inherit(const inheritable_diagram_options &parent); }; diff --git a/src/include_diagram/generators/plantuml/include_diagram_generator.cc b/src/include_diagram/generators/plantuml/include_diagram_generator.cc index 58ed09e4..cf696df9 100644 --- a/src/include_diagram/generators/plantuml/include_diagram_generator.cc +++ b/src/include_diagram/generators/plantuml/include_diagram_generator.cc @@ -31,11 +31,41 @@ void generator::generate_relationships( const source_file &f, std::ostream &ostr) const { LOG_DBG("Generating relationships for file {}", f.full_name(true)); + + namespace plantuml_common = clanguml::common::generators::plantuml; + + if (f.type() == common::model::source_file_t::kDirectory) { + for (const auto &file : f) { + generate_relationships( + dynamic_cast(*file), ostr); + } + } + else { + for (const auto &r : f.relationships()) { + if (m_model.should_include(r.type())) { + ostr << f.alias() << " " + << plantuml_common::to_plantuml(r.type(), r.style()) << " " + << r.destination() << '\n'; + } + } + } } void generator::generate(const source_file &f, std::ostream &ostr) const { LOG_DBG("Generating source_file {}", f.name()); + + if (f.type() == common::model::source_file_t::kDirectory) { + ostr << "folder " << f.name(); + ostr << " as " << f.alias() << " {\n"; + for (const auto &file : f) { + generate(dynamic_cast(*file), ostr); + } + ostr << "}" << '\n'; + } + else { + ostr << "file " << f.name() << " as " << f.alias() << '\n'; + } } void generator::generate(std::ostream &ostr) const @@ -44,15 +74,14 @@ void generator::generate(std::ostream &ostr) const generate_plantuml_directives(ostr, m_config.puml().before); + // Generate files and folders for (const auto &p : m_model) { generate(dynamic_cast(*p), ostr); - ostr << '\n'; } - // Process package relationships + // Process file include relationships for (const auto &p : m_model) { generate_relationships(dynamic_cast(*p), ostr); - ostr << '\n'; } generate_config_layout_hints(ostr); diff --git a/src/include_diagram/generators/plantuml/include_diagram_generator.h b/src/include_diagram/generators/plantuml/include_diagram_generator.h index 8b2967c3..314bde18 100644 --- a/src/include_diagram/generators/plantuml/include_diagram_generator.h +++ b/src/include_diagram/generators/plantuml/include_diagram_generator.h @@ -20,10 +20,10 @@ #include "common/generators/plantuml/generator.h" #include "common/model/package.h" #include "common/model/relationship.h" +#include "common/model/source_file.h" #include "config/config.h" #include "cx/compilation_database.h" #include "include_diagram/model/diagram.h" -#include "include_diagram/model/source_file.h" #include "include_diagram/visitor/translation_unit_visitor.h" #include "util/util.h" @@ -50,7 +50,7 @@ using common_generator = using clanguml::common::model::access_t; using clanguml::common::model::package; using clanguml::common::model::relationship_t; -using clanguml::include_diagram::model::source_file; +using clanguml::common::model::source_file; using namespace clanguml::util; class generator : public common_generator { diff --git a/src/include_diagram/model/diagram.cc b/src/include_diagram/model/diagram.cc index 3ad181f0..58023d60 100644 --- a/src/include_diagram/model/diagram.cc +++ b/src/include_diagram/model/diagram.cc @@ -34,16 +34,18 @@ type_safe::optional_ref diagram::get( return get_file(full_name); } -void diagram::add_file(std::unique_ptr &&f) +void diagram::add_file(std::unique_ptr &&f) { LOG_DBG("Adding source file: {}, {}", f->name(), f->full_name(true)); files_.emplace_back(*f); - add_element(f->path(), std::move(f)); + auto p = f->path(); + + add_element(p, std::move(f)); } -type_safe::optional_ref +type_safe::optional_ref diagram::get_file(const std::string &name) const { for (const auto &p : files_) { diff --git a/src/include_diagram/model/diagram.h b/src/include_diagram/model/diagram.h index 8c71bcd7..58ed1581 100644 --- a/src/include_diagram/model/diagram.h +++ b/src/include_diagram/model/diagram.h @@ -19,7 +19,7 @@ #include "common/model/diagram.h" #include "common/model/package.h" -#include "source_file.h" +#include "common/model/source_file.h" #include @@ -29,8 +29,9 @@ namespace clanguml::include_diagram::model { class diagram : public clanguml::common::model::diagram, - public clanguml::common::model::nested_trait { + public clanguml::common::model::nested_trait< + clanguml::common::model::source_file, + clanguml::common::model::filesystem_path> { public: diagram() = default; @@ -44,16 +45,15 @@ public: type_safe::optional_ref get( const std::string &full_name) const; - void add_file(std::unique_ptr &&f); + void add_file(std::unique_ptr &&f); - type_safe::optional_ref get_file( + type_safe::optional_ref get_file( const std::string &name) const; std::string to_alias(const std::string &full_name) const; private: - std::vector< - type_safe::object_ref> + std::vector> files_; }; } diff --git a/src/include_diagram/visitor/translation_unit_context.cc b/src/include_diagram/visitor/translation_unit_context.cc index a77e46aa..4cff5297 100644 --- a/src/include_diagram/visitor/translation_unit_context.cc +++ b/src/include_diagram/visitor/translation_unit_context.cc @@ -49,12 +49,12 @@ clanguml::include_diagram::model::diagram &translation_unit_context::diagram() } void translation_unit_context::set_current_file( - type_safe::optional_ref f) + type_safe::optional_ref f) { current_file_ = f; } -type_safe::optional_ref +type_safe::optional_ref translation_unit_context::get_current_file() const { return current_file_; diff --git a/src/include_diagram/visitor/translation_unit_context.h b/src/include_diagram/visitor/translation_unit_context.h index fe7cac41..5d72912a 100644 --- a/src/include_diagram/visitor/translation_unit_context.h +++ b/src/include_diagram/visitor/translation_unit_context.h @@ -41,9 +41,9 @@ public: clanguml::include_diagram::model::diagram &diagram(); void set_current_file( - type_safe::optional_ref p); + type_safe::optional_ref p); - type_safe::optional_ref + type_safe::optional_ref get_current_file() const; private: @@ -56,7 +56,7 @@ private: // Reference to class diagram config const clanguml::config::include_diagram &config_; - type_safe::optional_ref current_file_; + type_safe::optional_ref current_file_; }; } diff --git a/src/include_diagram/visitor/translation_unit_visitor.cc b/src/include_diagram/visitor/translation_unit_visitor.cc index d8c9ff2f..c453b309 100644 --- a/src/include_diagram/visitor/translation_unit_visitor.cc +++ b/src/include_diagram/visitor/translation_unit_visitor.cc @@ -21,9 +21,9 @@ #include #include -namespace clanguml::include_diagram::visitor { +#include -using clanguml::include_diagram::model::diagram; +namespace clanguml::include_diagram::visitor { translation_unit_visitor::translation_unit_visitor( cppast::cpp_entity_index &idx, @@ -35,19 +35,116 @@ translation_unit_visitor::translation_unit_visitor( void translation_unit_visitor::operator()(const cppast::cpp_entity &file) { + assert(file.kind() == cppast::cpp_entity_kind::file_t); + + const auto &f = static_cast(file); + + auto file_path = f.name(); + + LOG_DBG("Processing source file {}", file_path); + + process_file(file_path, true); + cppast::visit(file, [&, this](const cppast::cpp_entity &e, cppast::visitor_info info) { if (e.kind() == cppast::cpp_entity_kind::include_directive_t) { + assert(ctx.get_current_file().has_value()); + + auto file_path_cpy = file.name(); const auto &inc = static_cast(e); - auto file_name = std::filesystem::path(inc.full_path()); + LOG_DBG("Processing include directive {} in file {}", + inc.full_path(), ctx.get_current_file().value().name()); - auto f = std::make_unique(); - f->set_path(file_name.parent_path().string()); - f->set_name(file_name.filename().string()); + process_file(inc.full_path(), false, inc.include_kind()); } }); } + +void translation_unit_visitor::process_file(const std::string &file, + bool register_as_current, + std::optional include_kind) +{ + auto include_path = std::filesystem::path(file); + + const std::filesystem::path base_directory{ctx.config().base_directory()}; + + if (include_path.is_relative()) { + include_path = ctx.config().base_directory() / include_path; + } + + include_path = include_path.lexically_normal(); + + auto f_abs = std::make_unique(); + + if (include_path.is_absolute()) + f_abs->set_absolute(); + + f_abs->set_path(include_path.parent_path().string()); + f_abs->set_name(include_path.filename().string()); + + if (ctx.diagram().should_include(*f_abs)) { + if (ctx.config().relative_to) { + const std::filesystem::path relative_to{ctx.config().relative_to()}; + + include_path = std::filesystem::relative(include_path, relative_to); + } + + auto f = std::make_unique(); + + auto parent_path_str = include_path.parent_path().string(); + if (!parent_path_str.empty()) + f->set_path(parent_path_str); + f->set_name(include_path.filename().string()); + f->set_type(common::model::source_file_t::kHeader); + + if (register_as_current && + ctx.diagram().get_element(f->path() | f->name()).has_value()) { + // This file is already in the diagram (e.g. it was added through an + // include directive before it was visited by the parser) + ctx.set_current_file( + ctx.diagram().get_element(f->path() | f->name())); + return; + } + + if (!f->path().is_empty()) { + // Ensure parent path directories source_files + // are in the diagram + common::model::filesystem_path parent_path_so_far; + for (const auto &directory : f->path()) { + auto dir = std::make_unique(); + if (!parent_path_so_far.is_empty()) + dir->set_path(parent_path_so_far); + dir->set_name(directory); + dir->set_type(common::model::source_file_t::kDirectory); + + if (!ctx.diagram() + .get_element(parent_path_so_far | directory) + .has_value()) + ctx.diagram().add_file(std::move(dir)); + + parent_path_so_far.append(directory); + } + } + + if (!register_as_current) { + auto relationship_type = + common::model::relationship_t::kAssociation; + if (include_kind.has_value() && + include_kind.value() == cppast::cpp_include_kind::system) + relationship_type = common::model::relationship_t::kDependency; + + ctx.get_current_file().value().add_relationship( + common::model::relationship{relationship_type, f->alias()}); + } + + if (!ctx.diagram().get_element(f->path() | f->name()).has_value()) { + ctx.set_current_file(type_safe::opt_ref(*f)); + ctx.diagram().add_file(std::move(f)); + } + } + +} } diff --git a/src/include_diagram/visitor/translation_unit_visitor.h b/src/include_diagram/visitor/translation_unit_visitor.h index f0b1318f..b9347a6f 100644 --- a/src/include_diagram/visitor/translation_unit_visitor.h +++ b/src/include_diagram/visitor/translation_unit_visitor.h @@ -21,14 +21,15 @@ #include "cx/cursor.h" #include "include_diagram/model/diagram.h" #include "include_diagram/visitor/translation_unit_context.h" +#include "common/model/enums.h" +#include "common/model/package.h" #include #include #include +#include #include -#include -#include #include #include #include @@ -45,6 +46,9 @@ public: void operator()(const cppast::cpp_entity &file); private: + void process_file(const std::string &file, bool register_as_current = false, + std::optional include_kind = {}); + // ctx allows to track current visitor context, e.g. current namespace translation_unit_context ctx; }; diff --git a/src/util/util.cc b/src/util/util.cc index 55991c13..08fc0415 100644 --- a/src/util/util.cc +++ b/src/util/util.cc @@ -198,5 +198,37 @@ bool replace_all( return replaced; } +template <> +bool starts_with( + const std::filesystem::path &path, const std::filesystem::path &prefix) +{ + if(path == prefix) + return true; + + const int path_length = std::distance(std::begin(path), std::end(path)); + + auto last_nonempty_prefix_element = std::prev(std::find_if( + prefix.begin(), prefix.end(), [](auto &&n) { return n.empty(); })); + + int prefix_length = + std::distance(std::begin(prefix), last_nonempty_prefix_element); + + // Empty prefix always matches + if (prefix_length == 0) + return true; + + // Prefix longer then path never matches + if (prefix_length >= path_length) + return false; + + auto path_compare_end = path.begin(); + std::advance(path_compare_end, prefix_length); + + std::vector pref(prefix.begin(), last_nonempty_prefix_element); + std::vector pat(path.begin(), path_compare_end); + + return pref == pat; +} + } } diff --git a/src/util/util.h b/src/util/util.h index 4208c5c4..d12ad9af 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -143,23 +144,26 @@ template void append(std::vector &l, const std::vector &r) } /** - * @brief Checks if vector starts with a prefix. + * @brief Checks if container starts with a prefix. * - * @tparam T - * @param col - * @param prefix - * @return + * @tparam T e.g. std::vector + * @param con Container to be checked against prefix + * @param prefix Container, which specifies the prefix + * @return true if first prefix.size() elements of con are equal to prefix */ -template -bool starts_with(const std::vector &col, const std::vector &prefix) +template bool starts_with(const T &con, const T &prefix) { - if (prefix.size() > col.size()) + if (prefix.size() > con.size()) return false; - return std::vector(prefix.begin(), prefix.end()) == - std::vector(col.begin(), col.begin() + prefix.size()); + return T(prefix.begin(), prefix.end()) == + T(con.begin(), con.begin() + prefix.size()); } +template <> +bool starts_with( + const std::filesystem::path &path, const std::filesystem::path &prefix); + template bool ends_with(const std::vector &col, const std::vector &suffix) { diff --git a/tests/t40001/include/t40001_include1.h b/tests/t40001/include/t40001_include1.h new file mode 100644 index 00000000..734f33d2 --- /dev/null +++ b/tests/t40001/include/t40001_include1.h @@ -0,0 +1,9 @@ +#pragma once + +#include "lib1/lib1.h" + +namespace clanguml::t40001 { + +int foo() { return lib1::foo2(); } + +} \ No newline at end of file diff --git a/tests/t40001/t40001.cc b/tests/t40001/src/t40001.cc similarity index 77% rename from tests/t40001/t40001.cc rename to tests/t40001/src/t40001.cc index fe07ee65..9c227217 100644 --- a/tests/t40001/t40001.cc +++ b/tests/t40001/src/t40001.cc @@ -1,7 +1,7 @@ #include #include -#include "t40001_include1.h" +#include "include/t40001_include1.h" namespace clanguml { namespace t40001 { diff --git a/tests/t40001/t40001_include1.h b/tests/t40001/t40001_include1.h deleted file mode 100644 index 94dcfa4a..00000000 --- a/tests/t40001/t40001_include1.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -namespace clanguml::t40001 { - -int foo() { return 0; } - -} \ No newline at end of file