Fixed path filtering
This commit is contained in:
@@ -16,6 +16,8 @@ diagrams:
|
|||||||
include!: uml/class_model_class_diagram.yml
|
include!: uml/class_model_class_diagram.yml
|
||||||
sequence_model_class:
|
sequence_model_class:
|
||||||
include!: uml/sequence_model_class_diagram.yml
|
include!: uml/sequence_model_class_diagram.yml
|
||||||
|
main_sequence:
|
||||||
|
include!: uml/main_sequence_diagram.yml
|
||||||
sequence_diagram_visitor_sequence:
|
sequence_diagram_visitor_sequence:
|
||||||
include!: uml/sequence_diagram_visitor_sequence_diagram.yml
|
include!: uml/sequence_diagram_visitor_sequence_diagram.yml
|
||||||
class_diagram_generator_sequence:
|
class_diagram_generator_sequence:
|
||||||
|
|||||||
@@ -353,13 +353,25 @@ paths_filter::paths_filter(filter_t type, const std::filesystem::path &root,
|
|||||||
: filter_visitor{type}
|
: filter_visitor{type}
|
||||||
, root_{root}
|
, root_{root}
|
||||||
{
|
{
|
||||||
for (auto &&path : p) {
|
for (const auto &path : p) {
|
||||||
std::filesystem::path absolute_path;
|
std::filesystem::path absolute_path;
|
||||||
|
|
||||||
if (path.is_relative())
|
if (path.string().empty() || path.string() == ".")
|
||||||
|
absolute_path = root;
|
||||||
|
else if (path.is_relative())
|
||||||
absolute_path = root / path;
|
absolute_path = root / path;
|
||||||
|
else
|
||||||
|
absolute_path = path;
|
||||||
|
|
||||||
absolute_path = absolute_path.lexically_normal();
|
try {
|
||||||
|
absolute_path =
|
||||||
|
std::filesystem::canonical(absolute_path.lexically_normal());
|
||||||
|
}
|
||||||
|
catch (std::filesystem::filesystem_error &e) {
|
||||||
|
LOG_WARN("Cannot add non-existent path {} to paths filter",
|
||||||
|
path.string());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
paths_.emplace_back(std::move(absolute_path));
|
paths_.emplace_back(std::move(absolute_path));
|
||||||
}
|
}
|
||||||
@@ -430,7 +442,7 @@ void diagram_filter::init_filters(const config::diagram &c)
|
|||||||
filter_t::kInclusive, c.include().access));
|
filter_t::kInclusive, c.include().access));
|
||||||
|
|
||||||
add_inclusive_filter(std::make_unique<paths_filter>(
|
add_inclusive_filter(std::make_unique<paths_filter>(
|
||||||
filter_t::kInclusive, c.base_directory(), c.include().paths));
|
filter_t::kInclusive, c.relative_to(), c.include().paths));
|
||||||
|
|
||||||
// Include any of these matches even if one them does not match
|
// Include any of these matches even if one them does not match
|
||||||
std::vector<std::unique_ptr<filter_visitor>> element_filters;
|
std::vector<std::unique_ptr<filter_visitor>> element_filters;
|
||||||
@@ -474,21 +486,11 @@ void diagram_filter::init_filters(const config::diagram &c)
|
|||||||
|
|
||||||
for (auto &&path : c.include().dependants) {
|
for (auto &&path : c.include().dependants) {
|
||||||
std::filesystem::path dep_path{path};
|
std::filesystem::path dep_path{path};
|
||||||
if (dep_path.is_relative()) {
|
|
||||||
dep_path = c.base_directory() / path;
|
|
||||||
dep_path = relative(dep_path, c.relative_to());
|
|
||||||
}
|
|
||||||
|
|
||||||
dependants.emplace_back(dep_path.lexically_normal().string());
|
dependants.emplace_back(dep_path.lexically_normal().string());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto &&path : c.include().dependencies) {
|
for (auto &&path : c.include().dependencies) {
|
||||||
std::filesystem::path dep_path{path};
|
std::filesystem::path dep_path{path};
|
||||||
if (dep_path.is_relative()) {
|
|
||||||
dep_path = c.base_directory() / path;
|
|
||||||
dep_path = relative(dep_path, c.relative_to());
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies.emplace_back(dep_path.lexically_normal().string());
|
dependencies.emplace_back(dep_path.lexically_normal().string());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -518,7 +520,7 @@ void diagram_filter::init_filters(const config::diagram &c)
|
|||||||
filter_t::kExclusive, c.exclude().namespaces));
|
filter_t::kExclusive, c.exclude().namespaces));
|
||||||
|
|
||||||
add_exclusive_filter(std::make_unique<paths_filter>(
|
add_exclusive_filter(std::make_unique<paths_filter>(
|
||||||
filter_t::kExclusive, c.base_directory(), c.exclude().paths));
|
filter_t::kExclusive, c.relative_to(), c.exclude().paths));
|
||||||
|
|
||||||
add_exclusive_filter(std::make_unique<element_filter>(
|
add_exclusive_filter(std::make_unique<element_filter>(
|
||||||
filter_t::kExclusive, c.exclude().elements));
|
filter_t::kExclusive, c.exclude().elements));
|
||||||
|
|||||||
@@ -595,6 +595,11 @@ template <> struct convert<sequence_diagram> {
|
|||||||
get_option(node, rhs.combine_free_functions_into_file_participants);
|
get_option(node, rhs.combine_free_functions_into_file_participants);
|
||||||
get_option(node, rhs.relative_to);
|
get_option(node, rhs.relative_to);
|
||||||
get_option(node, rhs.participants_order);
|
get_option(node, rhs.participants_order);
|
||||||
|
get_option(node, rhs.generate_method_arguments);
|
||||||
|
|
||||||
|
// Ensure relative_to has a value
|
||||||
|
if (!rhs.relative_to.has_value)
|
||||||
|
rhs.relative_to.set(std::filesystem::current_path());
|
||||||
|
|
||||||
rhs.initialize_type_aliases();
|
rhs.initialize_type_aliases();
|
||||||
|
|
||||||
@@ -630,6 +635,9 @@ template <> struct convert<include_diagram> {
|
|||||||
get_option(node, rhs.relative_to);
|
get_option(node, rhs.relative_to);
|
||||||
get_option(node, rhs.generate_system_headers);
|
get_option(node, rhs.generate_system_headers);
|
||||||
|
|
||||||
|
if (!rhs.relative_to)
|
||||||
|
rhs.relative_to.set(std::filesystem::current_path());
|
||||||
|
|
||||||
// Convert the path in relative_to to an absolute path, with respect
|
// Convert the path in relative_to to an absolute path, with respect
|
||||||
// to the directory where the `.clang-uml` configuration file is located
|
// to the directory where the `.clang-uml` configuration file is located
|
||||||
if (rhs.relative_to) {
|
if (rhs.relative_to) {
|
||||||
|
|||||||
@@ -58,20 +58,28 @@ void generator::generate_call(const message &m, std::ostream &ostr) const
|
|||||||
|
|
||||||
std::string message;
|
std::string message;
|
||||||
|
|
||||||
|
model::function::message_render_mode render_mode =
|
||||||
|
model::function::message_render_mode::full;
|
||||||
|
|
||||||
|
if (m_config.generate_method_arguments() ==
|
||||||
|
config::method_arguments::abbreviated)
|
||||||
|
render_mode = model::function::message_render_mode::abbreviated;
|
||||||
|
else if (m_config.generate_method_arguments() ==
|
||||||
|
config::method_arguments::none)
|
||||||
|
render_mode = model::function::message_render_mode::no_arguments;
|
||||||
|
|
||||||
if (to.value().type_name() == "method") {
|
if (to.value().type_name() == "method") {
|
||||||
message = dynamic_cast<const model::function &>(to.value())
|
message = dynamic_cast<const model::function &>(to.value())
|
||||||
.message_name(model::function::message_render_mode::full);
|
.message_name(render_mode);
|
||||||
}
|
}
|
||||||
else if (m_config.combine_free_functions_into_file_participants()) {
|
else if (m_config.combine_free_functions_into_file_participants()) {
|
||||||
if (to.value().type_name() == "function") {
|
if (to.value().type_name() == "function") {
|
||||||
message =
|
message = dynamic_cast<const model::function &>(to.value())
|
||||||
dynamic_cast<const model::function &>(to.value())
|
.message_name(render_mode);
|
||||||
.message_name(model::function::message_render_mode::full);
|
|
||||||
}
|
}
|
||||||
else if (to.value().type_name() == "function_template") {
|
else if (to.value().type_name() == "function_template") {
|
||||||
message =
|
message = dynamic_cast<const model::function_template &>(to.value())
|
||||||
dynamic_cast<const model::function_template &>(to.value())
|
.message_name(render_mode);
|
||||||
.message_name(model::function::message_render_mode::full);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,7 +310,7 @@ void generator::generate_participant(
|
|||||||
if (is_participant_generated(file_id))
|
if (is_participant_generated(file_id))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
[[maybe_unused]] const auto &relative_to =
|
const auto &relative_to =
|
||||||
std::filesystem::canonical(m_config.relative_to());
|
std::filesystem::canonical(m_config.relative_to());
|
||||||
|
|
||||||
auto participant_name = std::filesystem::relative(
|
auto participant_name = std::filesystem::relative(
|
||||||
@@ -388,13 +396,22 @@ void generator::generate(std::ostream &ostr) const
|
|||||||
|
|
||||||
std::string from_alias = generate_alias(from.value());
|
std::string from_alias = generate_alias(from.value());
|
||||||
|
|
||||||
|
model::function::message_render_mode render_mode =
|
||||||
|
model::function::message_render_mode::full;
|
||||||
|
|
||||||
|
if (m_config.generate_method_arguments() ==
|
||||||
|
config::method_arguments::abbreviated)
|
||||||
|
render_mode = model::function::message_render_mode::abbreviated;
|
||||||
|
else if (m_config.generate_method_arguments() ==
|
||||||
|
config::method_arguments::none)
|
||||||
|
render_mode =
|
||||||
|
model::function::message_render_mode::no_arguments;
|
||||||
|
|
||||||
if (from.value().type_name() == "method" ||
|
if (from.value().type_name() == "method" ||
|
||||||
m_config.combine_free_functions_into_file_participants()) {
|
m_config.combine_free_functions_into_file_participants()) {
|
||||||
ostr << "[->"
|
ostr << "[->"
|
||||||
<< " " << from_alias << " : "
|
<< " " << from_alias << " : "
|
||||||
<< from.value().message_name(
|
<< from.value().message_name(render_mode) << std::endl;
|
||||||
model::function::message_render_mode::full)
|
|
||||||
<< std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ostr << "activate " << from_alias << std::endl;
|
ostr << "activate " << from_alias << std::endl;
|
||||||
|
|||||||
@@ -196,6 +196,12 @@ std::string function::message_name(message_render_mode mode) const
|
|||||||
if (mode == message_render_mode::no_arguments) {
|
if (mode == message_render_mode::no_arguments) {
|
||||||
return fmt::format("{}(){}", name(), is_const() ? " const" : "");
|
return fmt::format("{}(){}", name(), is_const() ? " const" : "");
|
||||||
}
|
}
|
||||||
|
else if (mode == message_render_mode::abbreviated) {
|
||||||
|
return fmt::format("{}({}){}", name(),
|
||||||
|
clanguml::util::abbreviate(
|
||||||
|
fmt::format("{}", fmt::join(parameters_, ",")), 15),
|
||||||
|
is_const() ? " const" : "");
|
||||||
|
}
|
||||||
|
|
||||||
return fmt::format("{}({}){}", name(), fmt::join(parameters_, ","),
|
return fmt::format("{}({}){}", name(), fmt::join(parameters_, ","),
|
||||||
is_const() ? " const" : "");
|
is_const() ? " const" : "");
|
||||||
@@ -259,6 +265,12 @@ std::string method::message_name(message_render_mode mode) const
|
|||||||
return fmt::format("{}{}(){}{}", style, method_name(),
|
return fmt::format("{}{}(){}{}", style, method_name(),
|
||||||
is_const() ? " const" : "", style);
|
is_const() ? " const" : "", style);
|
||||||
}
|
}
|
||||||
|
else if (mode == message_render_mode::abbreviated) {
|
||||||
|
return fmt::format("{}({}){}", name(),
|
||||||
|
clanguml::util::abbreviate(
|
||||||
|
fmt::format("{}", fmt::join(parameters(), ",")), 15),
|
||||||
|
is_const() ? " const" : "");
|
||||||
|
}
|
||||||
|
|
||||||
return fmt::format("{}{}({}){}{}", style, method_name(),
|
return fmt::format("{}{}({}){}{}", style, method_name(),
|
||||||
fmt::join(parameters(), ","), is_const() ? " const" : "", style);
|
fmt::join(parameters(), ","), is_const() ? " const" : "", style);
|
||||||
@@ -330,6 +342,12 @@ std::string function_template::message_name(message_render_mode mode) const
|
|||||||
return fmt::format(
|
return fmt::format(
|
||||||
"{}{}(){}", name(), template_params, is_const() ? " const" : "");
|
"{}{}(){}", name(), template_params, is_const() ? " const" : "");
|
||||||
}
|
}
|
||||||
|
else if (mode == message_render_mode::abbreviated) {
|
||||||
|
return fmt::format("{}({}){}", name(),
|
||||||
|
clanguml::util::abbreviate(
|
||||||
|
fmt::format("{}", fmt::join(parameters(), ",")), 15),
|
||||||
|
is_const() ? " const" : "");
|
||||||
|
}
|
||||||
|
|
||||||
return fmt::format("{}{}({}){}", name(), template_params,
|
return fmt::format("{}{}({}){}", name(), template_params,
|
||||||
fmt::join(parameters(), ","), is_const() ? " const" : "");
|
fmt::join(parameters(), ","), is_const() ? " const" : "");
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ struct lambda : public class_ {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct function : public participant {
|
struct function : public participant {
|
||||||
enum class message_render_mode { full, no_arguments };
|
enum class message_render_mode { full, abbreviated, no_arguments };
|
||||||
|
|
||||||
function(const common::model::namespace_ &using_namespace);
|
function(const common::model::namespace_ &using_namespace);
|
||||||
|
|
||||||
|
|||||||
@@ -274,7 +274,7 @@ bool translation_unit_visitor::VisitCXXMethodDecl(clang::CXXMethodDecl *m)
|
|||||||
LOG_DBG("Getting method's class with local id {}", parent_decl->getID());
|
LOG_DBG("Getting method's class with local id {}", parent_decl->getID());
|
||||||
|
|
||||||
if (!get_participant<model::class_>(parent_decl)) {
|
if (!get_participant<model::class_>(parent_decl)) {
|
||||||
LOG_INFO("Cannot find parent class_ for method {} in class {}",
|
LOG_DBG("Cannot find parent class_ for method {} in class {}",
|
||||||
m->getQualifiedNameAsString(),
|
m->getQualifiedNameAsString(),
|
||||||
m->getParent()->getQualifiedNameAsString());
|
m->getParent()->getQualifiedNameAsString());
|
||||||
return true;
|
return true;
|
||||||
@@ -490,10 +490,8 @@ bool translation_unit_visitor::VisitLambdaExpr(clang::LambdaExpr *expr)
|
|||||||
auto m_ptr = std::make_unique<sequence_diagram::model::method>(
|
auto m_ptr = std::make_unique<sequence_diagram::model::method>(
|
||||||
config().using_namespace());
|
config().using_namespace());
|
||||||
|
|
||||||
common::model::namespace_ ns{c_ptr->get_namespace()};
|
|
||||||
auto method_name = "operator()";
|
auto method_name = "operator()";
|
||||||
m_ptr->set_method_name(method_name);
|
m_ptr->set_method_name(method_name);
|
||||||
ns.pop_back();
|
|
||||||
|
|
||||||
m_ptr->set_class_id(cls_id);
|
m_ptr->set_class_id(cls_id);
|
||||||
m_ptr->set_class_full_name(c_ptr->full_name(false));
|
m_ptr->set_class_full_name(c_ptr->full_name(false));
|
||||||
@@ -1158,6 +1156,11 @@ bool translation_unit_visitor::process_function_call_expression(
|
|||||||
if (!diagram().should_include(callee_name))
|
if (!diagram().should_include(callee_name))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// Skip free functions declared in files outside of included paths
|
||||||
|
if (config().combine_free_functions_into_file_participants() &&
|
||||||
|
!diagram().should_include(common::model::source_file{m.file()}))
|
||||||
|
return false;
|
||||||
|
|
||||||
std::unique_ptr<model::function_template> f_ptr;
|
std::unique_ptr<model::function_template> f_ptr;
|
||||||
|
|
||||||
if (!get_unique_id(callee_function->getID()).has_value()) {
|
if (!get_unique_id(callee_function->getID()).has_value()) {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <numeric>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace clanguml {
|
namespace clanguml {
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ diagrams:
|
|||||||
include:
|
include:
|
||||||
namespaces:
|
namespaces:
|
||||||
- clanguml::t20017
|
- clanguml::t20017
|
||||||
|
# This only affects free function participants
|
||||||
|
paths:
|
||||||
|
- t20017.cc
|
||||||
using_namespace:
|
using_namespace:
|
||||||
- clanguml::t20017
|
- clanguml::t20017
|
||||||
start_from:
|
start_from:
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ diagrams:
|
|||||||
include:
|
include:
|
||||||
# Include only headers belonging to these paths
|
# Include only headers belonging to these paths
|
||||||
paths:
|
paths:
|
||||||
- ../../../tests/t40001
|
- .
|
||||||
plantuml:
|
plantuml:
|
||||||
before:
|
before:
|
||||||
- "' t40001 test include diagram"
|
- "' t40001 test include diagram"
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ diagrams:
|
|||||||
# Render the paths relative to this directory
|
# Render the paths relative to this directory
|
||||||
relative_to: ../../../tests/t40002
|
relative_to: ../../../tests/t40002
|
||||||
include:
|
include:
|
||||||
# Include only files belonging to these paths
|
# Include only files belonging to these paths relative to relative_to
|
||||||
paths:
|
paths:
|
||||||
- ../../../tests/t40002
|
- .
|
||||||
exclude:
|
exclude:
|
||||||
paths:
|
paths:
|
||||||
# Exclude single header
|
# Exclude single header relative to relative_to
|
||||||
- ../../../tests/t40002/include/lib2/lib2_detail.h
|
- include/lib2/lib2_detail.h
|
||||||
plantuml:
|
plantuml:
|
||||||
before:
|
before:
|
||||||
- "' t40002 test include diagram"
|
- "' t40002 test include diagram"
|
||||||
@@ -13,10 +13,10 @@ diagrams:
|
|||||||
include:
|
include:
|
||||||
# Include only files which depend on t1.h
|
# Include only files which depend on t1.h
|
||||||
dependants:
|
dependants:
|
||||||
- ../../../tests/t40003/include/dependants/t1.h
|
- include/dependants/t1.h
|
||||||
# and dependencies of t2.cc
|
# and dependencies of t2.cc
|
||||||
dependencies:
|
dependencies:
|
||||||
- ../../../tests/t40003/src/dependencies/t2.cc
|
- src/dependencies/t2.cc
|
||||||
plantuml:
|
plantuml:
|
||||||
before:
|
before:
|
||||||
- "' t40003 test include diagram"
|
- "' t40003 test include diagram"
|
||||||
@@ -3,18 +3,18 @@ output_directory: output
|
|||||||
|
|
||||||
diagrams:
|
diagrams:
|
||||||
include_test:
|
include_test:
|
||||||
type: class
|
type: include
|
||||||
|
relative_to: ../../../src
|
||||||
glob:
|
glob:
|
||||||
- src/**/*.cc
|
- src/**/*.cc
|
||||||
- src/**/*.h
|
- src/**/*.h
|
||||||
include:
|
include:
|
||||||
paths:
|
paths:
|
||||||
- dir1
|
- class_diagram/
|
||||||
- dir2/dir1
|
- common
|
||||||
- file1.h
|
- util
|
||||||
|
- main.cc
|
||||||
exclude:
|
exclude:
|
||||||
paths:
|
paths:
|
||||||
- dir1/file9.h
|
- sequence_diagram
|
||||||
- dir2/dir1/file9.h
|
- util/error.h
|
||||||
- dir1/dir3
|
|
||||||
- file9.h
|
|
||||||
@@ -32,11 +32,6 @@ TEST_CASE("Test diagram paths filter", "[unit-test]")
|
|||||||
using clanguml::common::model::diagram_filter;
|
using clanguml::common::model::diagram_filter;
|
||||||
using clanguml::common::model::source_file;
|
using clanguml::common::model::source_file;
|
||||||
|
|
||||||
auto make_path = [](std::string_view p) {
|
|
||||||
return source_file{
|
|
||||||
std::filesystem::current_path() / "test_config_data" / p};
|
|
||||||
};
|
|
||||||
|
|
||||||
auto cfg = clanguml::config::load("./test_config_data/filters.yml");
|
auto cfg = clanguml::config::load("./test_config_data/filters.yml");
|
||||||
|
|
||||||
CHECK(cfg.diagrams.size() == 1);
|
CHECK(cfg.diagrams.size() == 1);
|
||||||
@@ -45,10 +40,15 @@ TEST_CASE("Test diagram paths filter", "[unit-test]")
|
|||||||
|
|
||||||
diagram_filter filter(diagram, config);
|
diagram_filter filter(diagram, config);
|
||||||
|
|
||||||
CHECK(filter.should_include(make_path("dir1/file1.h")));
|
auto make_path = [&](std::string_view p) {
|
||||||
CHECK(filter.should_include(make_path("dir1/dir2/file1.h")));
|
return source_file{config.relative_to() / p};
|
||||||
CHECK(filter.should_include(make_path("dir1/dir2/dir3/dir4/file1.h")));
|
};
|
||||||
CHECK_FALSE(filter.should_include(make_path("dir1/file9.h")));
|
|
||||||
CHECK_FALSE(filter.should_include(make_path("dir1/dir3/file1.h")));
|
CHECK(filter.should_include(
|
||||||
CHECK_FALSE(filter.should_include(make_path("dir2/dir1/file9.h")));
|
make_path("class_diagram/visitor/translation_unit_visitor.h")));
|
||||||
|
CHECK(filter.should_include(make_path("main.cc")));
|
||||||
|
CHECK(filter.should_include(make_path("util/util.cc")));
|
||||||
|
CHECK_FALSE(filter.should_include(make_path("util/error.h")));
|
||||||
|
CHECK_FALSE(filter.should_include(
|
||||||
|
make_path("sequence_diagram/visitor/translation_unit_visitor.h")));
|
||||||
}
|
}
|
||||||
|
|||||||
30
uml/main_sequence_diagram.yml
Normal file
30
uml/main_sequence_diagram.yml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
type: sequence
|
||||||
|
combine_free_functions_into_file_participants: true
|
||||||
|
generate_method_arguments: none
|
||||||
|
glob:
|
||||||
|
- src/main.cc
|
||||||
|
include:
|
||||||
|
paths:
|
||||||
|
- src
|
||||||
|
exclude:
|
||||||
|
namespaces:
|
||||||
|
- std
|
||||||
|
- fmt
|
||||||
|
- spdlog
|
||||||
|
- clang
|
||||||
|
- llvm
|
||||||
|
- nlohmann
|
||||||
|
- CLI
|
||||||
|
- __gnu_cxx
|
||||||
|
- inja
|
||||||
|
elements:
|
||||||
|
- clanguml::config::option<std::string>
|
||||||
|
paths:
|
||||||
|
- src/util/util.h
|
||||||
|
using_namespace:
|
||||||
|
- clanguml
|
||||||
|
plantuml:
|
||||||
|
before:
|
||||||
|
- 'title clang-uml main function sequence diagram'
|
||||||
|
start_from:
|
||||||
|
- function: main(int,const char **)
|
||||||
Reference in New Issue
Block a user