Added options to render diagrams using plantuml and mermaidjs external tools

This commit is contained in:
Bartek Kryza
2023-11-12 22:57:37 +01:00
parent 429e1c47a9
commit 35f45a07e6
21 changed files with 266 additions and 40 deletions

View File

@@ -4,6 +4,10 @@ comment_parser: clang
remove_compile_flags: remove_compile_flags:
- -Wno-class-memaccess - -Wno-class-memaccess
- -Wno-dangling-reference - -Wno-dangling-reference
plantuml:
cmd: "plantuml -tsvg \"docs/diagrams/{}.puml\""
mermaid:
cmd: "mmdc -i \"docs/diagrams/{}.mmd\" -o \"docs/diagrams/{}.svg\""
generate_links: generate_links:
link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}"
tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}"
@@ -29,7 +33,7 @@ diagrams:
include!: uml/class/stylable_element_hierarchy_class.yml include!: uml/class/stylable_element_hierarchy_class.yml
source_location_hierarchy_class: source_location_hierarchy_class:
include!: uml/class/source_location_hierarchy_class.yml include!: uml/class/source_location_hierarchy_class.yml
filter_visitor_hierarchy_class: "filter_visitor_hierarchy_class":
include!: uml/class/filter_visitor_hierarchy_class.yml include!: uml/class/filter_visitor_hierarchy_class.yml
diagram_filter_context_class: diagram_filter_context_class:
include!: uml/class/diagram_filter_context_class.yml include!: uml/class/diagram_filter_context_class.yml

View File

@@ -43,6 +43,14 @@ will add before the diagram contents (right after `@startuml`) the title and
direction hint, and after each diagram contents (right before `@enduml`) direction hint, and after each diagram contents (right before `@enduml`)
2 notes attached to elements. 2 notes attached to elements.
This generator also accepts a `cmd` parameter to specify a command to execute
on the generated PlantUML source file to generate actual diagram image, for
instance:
```yaml
plantuml:
cmd: "/usr/bin/plantuml -tsvg \"diagrams/{}.puml\""
```
An example PlantUML diagram is presented below: An example PlantUML diagram is presented below:
```plantuml ```plantuml
@@ -117,6 +125,14 @@ will add before the diagram contents (right after diagram type,
e.g. `classDiagram`) diagram direction hint, and after each diagram contents e.g. `classDiagram`) diagram direction hint, and after each diagram contents
2 notes attached to elements. 2 notes attached to elements.
This generator also accepts a `cmd` parameter to specify a command to execute
on the generated MermaidJS source file to generate actual diagram image, for
instance:
```yaml
mermaid:
cmd: "mmdc -i \"diagrams/{}.mmd\" -o \"diagrams/{}_mermaid.svg\""
```
An example MermaidJS diagram is presented below: An example MermaidJS diagram is presented below:
``` ```

View File

@@ -52,6 +52,12 @@ To add an initial class diagram to your project, follow these steps:
mmdc -i diagrams/some_class_diagram.mmd -o diagrams/some_class_diagram.svg mmdc -i diagrams/some_class_diagram.mmd -o diagrams/some_class_diagram.svg
``` ```
Steps 3 and 4 can be combined into one step like follows:
```
clang-uml -p -n some_class_diagram -g plantuml -r --plantuml-cmd="plantuml -tsvg diagrams/{}.puml"
```
where `-r` enables diagram rendering and `--plantuml-cmd` specifies command
to execute on each generated diagram.
5. Add another diagram: 5. Add another diagram:
```bash ```bash
clang-uml --add-sequence-diagram another_diagram clang-uml --add-sequence-diagram another_diagram

View File

@@ -138,6 +138,14 @@ cli_flow_t cli_handler::parse(int argc, const char **argv)
"Do not perform configuration file schema validation"); "Do not perform configuration file schema validation");
app.add_flag("--validate-only", validate_only, app.add_flag("--validate-only", validate_only,
"Perform configuration file schema validation and exit"); "Perform configuration file schema validation and exit");
app.add_flag("-r,--render_diagrams", render_diagrams,
"Automatically render generated diagrams using appropriate command");
app.add_option("--plantuml-cmd", plantuml_cmd,
"Command template to render PlantUML diagram, `{}` will be replaced "
"with diagram name.");
app.add_option("--mermaid-cmd", mermaid_cmd,
"Command template to render MermaidJS diagram, `{}` will be replaced "
"with diagram name.");
try { try {
app.parse(argc, argv); app.parse(argc, argv);
@@ -184,6 +192,8 @@ cli_flow_t cli_handler::handle_options(int argc, const char **argv)
res = handle_post_config_options(); res = handle_post_config_options();
config.inherit();
return res; return res;
} }
@@ -250,8 +260,8 @@ cli_flow_t cli_handler::handle_pre_config_options()
cli_flow_t cli_handler::load_config() cli_flow_t cli_handler::load_config()
{ {
try { try {
config = clanguml::config::load( config = clanguml::config::load(config_path, false,
config_path, paths_relative_to_pwd, no_metadata, !no_validate); paths_relative_to_pwd, no_metadata, !no_validate);
if (validate_only) { if (validate_only) {
std::cout << "Configuration file " << config_path << " is valid.\n"; std::cout << "Configuration file " << config_path << " is valid.\n";
@@ -334,6 +344,20 @@ cli_flow_t cli_handler::handle_post_config_options()
config.remove_compile_flags.has_value = true; config.remove_compile_flags.has_value = true;
} }
if (plantuml_cmd) {
if (!config.puml)
config.puml.set({});
config.puml().cmd = plantuml_cmd.value();
}
if (mermaid_cmd) {
if (!config.mermaid)
config.mermaid.set({});
config.mermaid().cmd = mermaid_cmd.value();
}
#if !defined(_WIN32) #if !defined(_WIN32)
if (query_driver) { if (query_driver) {
config.query_driver.set(*query_driver); config.query_driver.set(*query_driver);
@@ -352,6 +376,8 @@ runtime_config cli_handler::get_runtime_config() const
cfg.print_to = print_to; cfg.print_to = print_to;
cfg.progress = progress; cfg.progress = progress;
cfg.thread_count = thread_count; cfg.thread_count = thread_count;
cfg.render_diagrams = render_diagrams;
cfg.output_directory = effective_output_directory;
return cfg; return cfg;
} }

View File

@@ -37,6 +37,8 @@ struct runtime_config {
bool print_to{}; bool print_to{};
bool progress{}; bool progress{};
unsigned int thread_count{}; unsigned int thread_count{};
bool render_diagrams{};
std::string output_directory{};
}; };
/** /**
@@ -185,6 +187,9 @@ public:
clanguml::common::generator_type_t::plantuml}; clanguml::common::generator_type_t::plantuml};
bool no_validate{false}; bool no_validate{false};
bool validate_only{false}; bool validate_only{false};
bool render_diagrams{false};
std::optional<std::string> plantuml_cmd;
std::optional<std::string> mermaid_cmd;
clanguml::config::config config; clanguml::config::config config;

View File

@@ -57,6 +57,35 @@ void find_translation_units_for_diagrams(
} }
} }
void render_diagram(const clanguml::common::generator_type_t generator_type,
const std::string &output_directory,
std::shared_ptr<config::diagram> diagram_config)
{
std::string cmd;
switch (generator_type) {
case clanguml::common::generator_type_t::plantuml:
cmd = diagram_config->puml().cmd;
break;
case clanguml::common::generator_type_t::mermaid:
cmd = diagram_config->mermaid().cmd;
break;
default:
return;
};
if (cmd.empty())
throw std::runtime_error(
fmt::format("No render command template provided for {} diagrams",
to_string(diagram_config->type())));
util::replace_all(cmd, "{}", diagram_config->name);
LOG_INFO("Rendering diagram {} using {}", diagram_config->name,
to_string(generator_type));
util::check_process_output(cmd);
}
namespace detail { namespace detail {
template <typename DiagramConfig, typename GeneratorTag, typename DiagramModel> template <typename DiagramConfig, typename GeneratorTag, typename DiagramModel>
@@ -79,11 +108,11 @@ void generate_diagram_select_generator(const std::string &od,
} }
template <typename DiagramConfig> template <typename DiagramConfig>
void generate_diagram_impl(const std::string &od, const std::string &name, void generate_diagram_impl(const std::string &name,
std::shared_ptr<clanguml::config::diagram> diagram, std::shared_ptr<clanguml::config::diagram> diagram,
const common::compilation_database &db, const common::compilation_database &db,
const std::vector<std::string> &translation_units, const std::vector<std::string> &translation_units,
const cli::runtime_config &rc, std::function<void()> &&progress) const cli::runtime_config &runtime_config, std::function<void()> &&progress)
{ {
using diagram_config = DiagramConfig; using diagram_config = DiagramConfig;
using diagram_model = typename diagram_model_t<DiagramConfig>::type; using diagram_model = typename diagram_model_t<DiagramConfig>::type;
@@ -91,11 +120,11 @@ void generate_diagram_impl(const std::string &od, const std::string &name,
auto model = clanguml::common::generators::generate<diagram_model, auto model = clanguml::common::generators::generate<diagram_model,
diagram_config, diagram_visitor>(db, diagram->name, diagram_config, diagram_visitor>(db, diagram->name,
dynamic_cast<diagram_config &>(*diagram), translation_units, rc.verbose, dynamic_cast<diagram_config &>(*diagram), translation_units,
std::move(progress)); runtime_config.verbose, std::move(progress));
if constexpr (std::is_same_v<DiagramConfig, config::sequence_diagram>) { if constexpr (std::is_same_v<DiagramConfig, config::sequence_diagram>) {
if (rc.print_from) { if (runtime_config.print_from) {
auto from_values = model->list_from_values(); auto from_values = model->list_from_values();
for (const auto &from : from_values) { for (const auto &from : from_values) {
@@ -104,7 +133,7 @@ void generate_diagram_impl(const std::string &od, const std::string &name,
return; return;
} }
if (rc.print_to) { if (runtime_config.print_to) {
auto to_values = model->list_to_values(); auto to_values = model->list_to_values();
for (const auto &to : to_values) { for (const auto &to : to_values) {
@@ -115,24 +144,32 @@ void generate_diagram_impl(const std::string &od, const std::string &name,
} }
} }
for (const auto generator_type : rc.generators) { for (const auto generator_type : runtime_config.generators) {
if (generator_type == generator_type_t::plantuml) { if (generator_type == generator_type_t::plantuml) {
generate_diagram_select_generator<diagram_config, generate_diagram_select_generator<diagram_config,
plantuml_generator_tag>(od, name, diagram, model); plantuml_generator_tag>(
runtime_config.output_directory, name, diagram, model);
} }
else if (generator_type == generator_type_t::json) { else if (generator_type == generator_type_t::json) {
generate_diagram_select_generator<diagram_config, generate_diagram_select_generator<diagram_config,
json_generator_tag>(od, name, diagram, model); json_generator_tag>(
runtime_config.output_directory, name, diagram, model);
} }
else if (generator_type == generator_type_t::mermaid) { else if (generator_type == generator_type_t::mermaid) {
generate_diagram_select_generator<diagram_config, generate_diagram_select_generator<diagram_config,
mermaid_generator_tag>(od, name, diagram, model); mermaid_generator_tag>(
runtime_config.output_directory, name, diagram, model);
}
if (runtime_config.render_diagrams) {
render_diagram(
generator_type, runtime_config.output_directory, diagram);
} }
} }
} }
} // namespace detail } // namespace detail
void generate_diagram(const std::string &od, const std::string &name, void generate_diagram(const std::string &name,
std::shared_ptr<clanguml::config::diagram> diagram, std::shared_ptr<clanguml::config::diagram> diagram,
const common::compilation_database &db, const common::compilation_database &db,
const std::vector<std::string> &translation_units, const std::vector<std::string> &translation_units,
@@ -147,26 +184,25 @@ void generate_diagram(const std::string &od, const std::string &name,
using clanguml::config::sequence_diagram; using clanguml::config::sequence_diagram;
if (diagram->type() == diagram_t::kClass) { if (diagram->type() == diagram_t::kClass) {
detail::generate_diagram_impl<class_diagram>(od, name, diagram, db, detail::generate_diagram_impl<class_diagram>(name, diagram, db,
translation_units, runtime_config, std::move(progress)); translation_units, runtime_config, std::move(progress));
} }
else if (diagram->type() == diagram_t::kSequence) { else if (diagram->type() == diagram_t::kSequence) {
detail::generate_diagram_impl<sequence_diagram>(od, name, diagram, db, detail::generate_diagram_impl<sequence_diagram>(name, diagram, db,
translation_units, runtime_config, std::move(progress)); translation_units, runtime_config, std::move(progress));
} }
else if (diagram->type() == diagram_t::kPackage) { else if (diagram->type() == diagram_t::kPackage) {
detail::generate_diagram_impl<package_diagram>(od, name, diagram, db, detail::generate_diagram_impl<package_diagram>(name, diagram, db,
translation_units, runtime_config, std::move(progress)); translation_units, runtime_config, std::move(progress));
} }
else if (diagram->type() == diagram_t::kInclude) { else if (diagram->type() == diagram_t::kInclude) {
detail::generate_diagram_impl<include_diagram>(od, name, diagram, db, detail::generate_diagram_impl<include_diagram>(name, diagram, db,
translation_units, runtime_config, std::move(progress)); translation_units, runtime_config, std::move(progress));
} }
} }
void generate_diagrams(const std::vector<std::string> &diagram_names, void generate_diagrams(const std::vector<std::string> &diagram_names,
config::config &config, const std::string &od, config::config &config, const common::compilation_database_ptr &db,
const common::compilation_database_ptr &db,
const cli::runtime_config &runtime_config, const cli::runtime_config &runtime_config,
const std::map<std::string, std::vector<std::string>> const std::map<std::string, std::vector<std::string>>
&translation_units_map) &translation_units_map)
@@ -206,7 +242,7 @@ void generate_diagrams(const std::vector<std::string> &diagram_names,
continue; continue;
} }
auto generator = [&od, &name = name, &diagram = diagram, &indicator, auto generator = [&name = name, &diagram = diagram, &indicator,
db = std::ref(*db), db = std::ref(*db),
translation_units = valid_translation_units, translation_units = valid_translation_units,
runtime_config]() mutable { runtime_config]() mutable {
@@ -215,7 +251,7 @@ void generate_diagrams(const std::vector<std::string> &diagram_names,
indicator->add_progress_bar(name, translation_units.size(), indicator->add_progress_bar(name, translation_units.size(),
diagram_type_to_color(diagram->type())); diagram_type_to_color(diagram->type()));
generate_diagram(od, name, diagram, db, translation_units, generate_diagram(name, diagram, db, translation_units,
runtime_config, [&indicator, &name]() { runtime_config, [&indicator, &name]() {
if (indicator) if (indicator)
indicator->increment(name); indicator->increment(name);

View File

@@ -418,7 +418,7 @@ void generate_diagram(const std::string &od, const std::string &name,
* *
* @param diagram_names List of diagram names to generate * @param diagram_names List of diagram names to generate
* @param config Reference to config instance * @param config Reference to config instance
* @param od Path to output directory * @param output_directory Path to output directory
* @param db Reference to compilation database * @param db Reference to compilation database
* @param verbose Log level * @param verbose Log level
* @param thread_count Number of diagrams to be generated in parallel * @param thread_count Number of diagrams to be generated in parallel
@@ -427,7 +427,7 @@ void generate_diagram(const std::string &od, const std::string &name,
* @param translation_units_map Map of translation units for each file * @param translation_units_map Map of translation units for each file
*/ */
void generate_diagrams(const std::vector<std::string> &diagram_names, void generate_diagrams(const std::vector<std::string> &diagram_names,
clanguml::config::config &config, const std::string &od, clanguml::config::config &config,
const common::compilation_database_ptr &db, const common::compilation_database_ptr &db,
const cli::runtime_config &runtime_config, const cli::runtime_config &runtime_config,
const std::map<std::string, std::vector<std::string>> const std::map<std::string, std::vector<std::string>>

View File

@@ -24,4 +24,17 @@ std::string to_string(const std::string &s) { return s; }
std::string to_string(const string_or_regex &sr) { return sr.to_string(); } std::string to_string(const string_or_regex &sr) { return sr.to_string(); }
}; std::string to_string(const generator_type_t type)
{
switch (type) {
case generator_type_t::plantuml:
return "plantuml";
case generator_type_t::mermaid:
return "mermaid";
case generator_type_t::json:
return "json";
default:
return "<unknown>";
}
}
} // namespace clanguml::common

View File

@@ -42,6 +42,8 @@ enum class generator_type_t {
std::string to_string(const std::string &s); std::string to_string(const std::string &s);
std::string to_string(generator_type_t type);
/** /**
* @brief Simple optional reference type. * @brief Simple optional reference type.
* *

View File

@@ -170,12 +170,16 @@ void plantuml::append(const plantuml &r)
{ {
before.insert(before.end(), r.before.begin(), r.before.end()); before.insert(before.end(), r.before.begin(), r.before.end());
after.insert(after.end(), r.after.begin(), r.after.end()); after.insert(after.end(), r.after.begin(), r.after.end());
if (cmd.empty())
cmd = r.cmd;
} }
void mermaid::append(const mermaid &r) void mermaid::append(const mermaid &r)
{ {
before.insert(before.end(), r.before.begin(), r.before.end()); before.insert(before.end(), r.before.begin(), r.before.end());
after.insert(after.end(), r.after.begin(), r.after.end()); after.insert(after.end(), r.after.begin(), r.after.end());
if (cmd.empty())
cmd = r.cmd;
} }
void inheritable_diagram_options::inherit( void inheritable_diagram_options::inherit(
@@ -188,6 +192,7 @@ void inheritable_diagram_options::inherit(
include.override(parent.include); include.override(parent.include);
exclude.override(parent.exclude); exclude.override(parent.exclude);
puml.override(parent.puml); puml.override(parent.puml);
mermaid.override(parent.mermaid);
generate_method_arguments.override(parent.generate_method_arguments); generate_method_arguments.override(parent.generate_method_arguments);
generate_packages.override(parent.generate_packages); generate_packages.override(parent.generate_packages);
generate_template_argument_dependencies.override( generate_template_argument_dependencies.override(

View File

@@ -121,6 +121,8 @@ struct plantuml {
std::vector<std::string> before; std::vector<std::string> before;
/*! List of directives to add before diagram */ /*! List of directives to add before diagram */
std::vector<std::string> after; std::vector<std::string> after;
/*! Command template to render diagram using PlantUML */
std::string cmd;
void append(const plantuml &r); void append(const plantuml &r);
}; };
@@ -137,6 +139,8 @@ struct mermaid {
std::vector<std::string> before; std::vector<std::string> before;
/*! List of directives to add before diagram */ /*! List of directives to add before diagram */
std::vector<std::string> after; std::vector<std::string> after;
/*! Command template to render diagram using MermaidJS */
std::string cmd;
void append(const mermaid &r); void append(const mermaid &r);
}; };
@@ -448,6 +452,10 @@ struct source_location {
struct inheritable_diagram_options { struct inheritable_diagram_options {
virtual ~inheritable_diagram_options() = default; virtual ~inheritable_diagram_options() = default;
void inherit(const inheritable_diagram_options &parent);
std::string simplify_template_type(std::string full_name) const;
option<std::vector<std::string>> glob{"glob"}; option<std::vector<std::string>> glob{"glob"};
option<common::model::namespace_> using_namespace{"using_namespace"}; option<common::model::namespace_> using_namespace{"using_namespace"};
option<bool> include_relations_also_as_members{ option<bool> include_relations_also_as_members{
@@ -494,10 +502,6 @@ struct inheritable_diagram_options {
"message_comment_width", clanguml::util::kDefaultMessageCommentWidth}; "message_comment_width", clanguml::util::kDefaultMessageCommentWidth};
option<bool> debug_mode{"debug_mode", false}; option<bool> debug_mode{"debug_mode", false};
option<bool> generate_metadata{"generate_metadata", true}; option<bool> generate_metadata{"generate_metadata", true};
void inherit(const inheritable_diagram_options &parent);
std::string simplify_template_type(std::string full_name) const;
}; };
/** /**
@@ -648,6 +652,8 @@ struct config : public inheritable_diagram_options {
* Initialize predefined diagram templates. * Initialize predefined diagram templates.
*/ */
void initialize_diagram_templates(); void initialize_diagram_templates();
void inherit();
}; };
/** /**
@@ -659,6 +665,7 @@ struct config : public inheritable_diagram_options {
* @embed{load_config_sequence.svg} * @embed{load_config_sequence.svg}
* *
* @param config_file Path to the configuration file * @param config_file Path to the configuration file
* @param inherit If true, common options will be propagated to diagram configs
* @param paths_relative_to_pwd Whether the paths in the configuration file * @param paths_relative_to_pwd Whether the paths in the configuration file
* should be relative to the parent directory of * should be relative to the parent directory of
* the configuration file or to the current * the configuration file or to the current
@@ -667,7 +674,7 @@ struct config : public inheritable_diagram_options {
* @param validate If true, perform schema validation * @param validate If true, perform schema validation
* @return Configuration instance * @return Configuration instance
*/ */
config load(const std::string &config_file, config load(const std::string &config_file, bool inherit = true,
std::optional<bool> paths_relative_to_pwd = {}, std::optional<bool> paths_relative_to_pwd = {},
std::optional<bool> no_metadata = {}, bool validate = true); std::optional<bool> no_metadata = {}, bool validate = true);

View File

@@ -24,6 +24,7 @@ namespace clanguml {
namespace config { namespace config {
template <typename T> void append_value(T &l, const T &r) { l = r; } template <typename T> void append_value(T &l, const T &r) { l = r; }
/** /**
* Possible option inheritance methods from top level to diagram level. * Possible option inheritance methods from top level to diagram level.
*/ */

View File

@@ -157,9 +157,11 @@ types:
plantuml: !optional plantuml: !optional
before: !optional [string] before: !optional [string]
after: !optional [string] after: !optional [string]
cmd: !optional string
mermaid: !optional mermaid: !optional
before: !optional [string] before: !optional [string]
after: !optional [string] after: !optional [string]
cmd: !optional string
relative_to: !optional string relative_to: !optional string
using_namespace: !optional [string, [string]] using_namespace: !optional [string, [string]]
generate_metadata: !optional bool generate_metadata: !optional bool
@@ -194,9 +196,11 @@ types:
plantuml: !optional plantuml: !optional
before: !optional [string] before: !optional [string]
after: !optional [string] after: !optional [string]
cmd: !optional string
mermaid: !optional mermaid: !optional
before: !optional [string] before: !optional [string]
after: !optional [string] after: !optional [string]
cmd: !optional string
relative_to: !optional string relative_to: !optional string
using_namespace: !optional [string, [string]] using_namespace: !optional [string, [string]]
generate_metadata: !optional bool generate_metadata: !optional bool
@@ -231,9 +235,11 @@ types:
plantuml: !optional plantuml: !optional
before: !optional [string] before: !optional [string]
after: !optional [string] after: !optional [string]
cmd: !optional string
mermaid: !optional mermaid: !optional
before: !optional [string] before: !optional [string]
after: !optional [string] after: !optional [string]
cmd: !optional string
relative_to: !optional string relative_to: !optional string
using_namespace: !optional [string, [string]] using_namespace: !optional [string, [string]]
generate_metadata: !optional bool generate_metadata: !optional bool
@@ -260,9 +266,11 @@ types:
plantuml: !optional plantuml: !optional
before: !optional [string] before: !optional [string]
after: !optional [string] after: !optional [string]
cmd: !optional string
mermaid: !optional mermaid: !optional
before: !optional [string] before: !optional [string]
after: !optional [string] after: !optional [string]
cmd: !optional string
relative_to: !optional string relative_to: !optional string
using_namespace: !optional [string, [string]] using_namespace: !optional [string, [string]]
generate_metadata: !optional bool generate_metadata: !optional bool
@@ -306,9 +314,11 @@ root:
plantuml: !optional plantuml: !optional
before: !optional [string] before: !optional [string]
after: !optional [string] after: !optional [string]
cmd: !optional string
mermaid: !optional mermaid: !optional
before: !optional [string] before: !optional [string]
after: !optional [string] after: !optional [string]
cmd: !optional string
relative_to: !optional string relative_to: !optional string
using_namespace: !optional [string, [string]] using_namespace: !optional [string, [string]]
generate_metadata: !optional bool generate_metadata: !optional bool

View File

@@ -387,6 +387,10 @@ template <> struct convert<plantuml> {
if (node["after"]) if (node["after"])
rhs.after = node["after"].as<decltype(rhs.after)>(); rhs.after = node["after"].as<decltype(rhs.after)>();
if (node["cmd"])
rhs.cmd = node["cmd"].as<decltype(rhs.cmd)>();
return true; return true;
} }
}; };
@@ -399,6 +403,10 @@ template <> struct convert<mermaid> {
if (node["after"]) if (node["after"])
rhs.after = node["after"].as<decltype(rhs.after)>(); rhs.after = node["after"].as<decltype(rhs.after)>();
if (node["cmd"])
rhs.cmd = node["cmd"].as<decltype(rhs.cmd)>();
return true; return true;
} }
}; };
@@ -831,7 +839,6 @@ template <> struct convert<config> {
diagram_config = parse_diagram_config(d.second); diagram_config = parse_diagram_config(d.second);
if (diagram_config) { if (diagram_config) {
diagram_config->name = name; diagram_config->name = name;
diagram_config->inherit(rhs);
rhs.diagrams[name] = diagram_config; rhs.diagrams[name] = diagram_config;
} }
else { else {
@@ -860,6 +867,13 @@ void config::initialize_diagram_templates()
predefined_templates.as<std::map<std::string, diagram_template>>()); predefined_templates.as<std::map<std::string, diagram_template>>());
} }
void config::inherit()
{
for (auto &[name, diagram] : diagrams) {
diagram->inherit(*this);
}
}
namespace { namespace {
void resolve_option_path(YAML::Node &doc, const std::string &option) void resolve_option_path(YAML::Node &doc, const std::string &option)
{ {
@@ -881,7 +895,7 @@ void resolve_option_path(YAML::Node &doc, const std::string &option)
} }
} // namespace } // namespace
config load(const std::string &config_file, config load(const std::string &config_file, bool inherit,
std::optional<bool> paths_relative_to_pwd, std::optional<bool> no_metadata, std::optional<bool> paths_relative_to_pwd, std::optional<bool> no_metadata,
bool validate) bool validate)
{ {
@@ -997,6 +1011,9 @@ config load(const std::string &config_file,
auto d = doc.as<config>(); auto d = doc.as<config>();
if (inherit)
d.inherit();
d.initialize_diagram_templates(); d.initialize_diagram_templates();
return d; return d;

View File

@@ -67,7 +67,7 @@ int main(int argc, const char *argv[])
const auto compilation_database_files = db->getAllFiles(); const auto compilation_database_files = db->getAllFiles();
std::map<std::string /* diagram name */, std::map<std::string /* diagram name */,
std::vector<std::string> /*translation units*/> std::vector<std::string> /* translation units */>
translation_units_map; translation_units_map;
// We have to generate the translation units list for each diagram // We have to generate the translation units list for each diagram
@@ -77,9 +77,8 @@ int main(int argc, const char *argv[])
cli.diagram_names, cli.config, compilation_database_files, cli.diagram_names, cli.config, compilation_database_files,
translation_units_map); translation_units_map);
common::generators::generate_diagrams(cli.diagram_names, cli.config, common::generators::generate_diagrams(cli.diagram_names, cli.config, db,
cli.effective_output_directory, db, cli.get_runtime_config(), cli.get_runtime_config(), translation_units_map);
translation_units_map);
} }
catch (error::compilation_database_error &e) { catch (error::compilation_database_error &e) {
LOG_ERROR("Failed to load compilation database from {} due to: {}", LOG_ERROR("Failed to load compilation database from {} due to: {}",
@@ -87,7 +86,7 @@ int main(int argc, const char *argv[])
return 1; return 1;
} }
catch (error::query_driver_no_paths &e) { catch (error::query_driver_no_paths &e) {
LOG_ERROR("Quering provided compiler driver {} did not provide any " LOG_ERROR("Querying provided compiler driver {} did not provide any "
"paths, please make sure the path is correct and that your " "paths, please make sure the path is correct and that your "
"compiler is GCC-compatible: {}", "compiler is GCC-compatible: {}",
cli.config.query_driver(), e.what()); cli.config.query_driver(), e.what());

View File

@@ -35,7 +35,7 @@ std::string get_process_output(const std::string &command)
std::string result; std::string result;
#if defined(__linux) || defined(__unix) || defined(__APPLE__) #if defined(__linux) || defined(__unix) || defined(__APPLE__)
const std::unique_ptr<FILE, decltype(&pclose)> pipe( std::unique_ptr<FILE, decltype(&pclose)> pipe(
popen(command.c_str(), "r"), pclose); popen(command.c_str(), "r"), pclose);
#elif defined(_WIN32) #elif defined(_WIN32)
std::unique_ptr<FILE, decltype(&_pclose)> pipe( std::unique_ptr<FILE, decltype(&_pclose)> pipe(
@@ -53,6 +53,38 @@ std::string get_process_output(const std::string &command)
return result; return result;
} }
void check_process_output(const std::string &command)
{
constexpr size_t kBufferSize{1024};
std::array<char, kBufferSize> buffer{};
int result{EXIT_FAILURE};
std::string output;
auto finalize = [&result](FILE *f) { result = pclose(f); };
#if defined(__linux) || defined(__unix) || defined(__APPLE__)
std::unique_ptr<FILE, decltype(finalize)> pipe(
popen(command.c_str(), "r"), finalize);
#elif defined(_WIN32)
std::unique_ptr<FILE, decltype(finalize)> pipe(
_popen(command.c_str(), "r"), finalize);
#endif
if (!pipe) {
throw std::runtime_error("popen() failed!");
}
while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
output += buffer.data();
}
pipe.reset();
if (result != EXIT_SUCCESS) {
throw std::runtime_error(
fmt::format("External command '{}' failed: {}", command, output));
}
}
std::string get_env(const std::string &name) std::string get_env(const std::string &name)
{ {
#if defined(__linux) || defined(__unix) #if defined(__linux) || defined(__unix)

View File

@@ -98,6 +98,13 @@ std::string trim_typename(const std::string &s);
*/ */
std::string get_process_output(const std::string &command); std::string get_process_output(const std::string &command);
/**
* @brief Execute command shell and throw exception if command fails
*
* @param command Command to execute
*/
void check_process_output(const std::string &command);
/** /**
* @brief Get value of an environment variable * @brief Get value of an environment variable
* *

View File

@@ -41,7 +41,7 @@ load_config(const std::string &test_name)
clanguml::common::compilation_database_ptr> clanguml::common::compilation_database_ptr>
res; res;
res.first = clanguml::config::load(test_name + "/.clang-uml", true); res.first = clanguml::config::load(test_name + "/.clang-uml", true, true);
LOG_DBG("Loading compilation database from {}", LOG_DBG("Loading compilation database from {}",
res.first.compilation_database_dir()); res.first.compilation_database_dir());

View File

@@ -184,3 +184,31 @@ TEST_CASE(
REQUIRE(contains(cli.config.add_compile_flags(), "-Wno-warning")); REQUIRE(contains(cli.config.add_compile_flags(), "-Wno-warning"));
REQUIRE(contains(cli.config.remove_compile_flags(), "-I/usr/include")); REQUIRE(contains(cli.config.remove_compile_flags(), "-I/usr/include"));
} }
TEST_CASE(
"Test cli handler puml config inheritance with render cmd", "[unit-test]")
{
using clanguml::cli::cli_flow_t;
using clanguml::cli::cli_handler;
using clanguml::util::contains;
std::vector<const char *> argv{"clang-uml", "--config",
"./test_config_data/render_cmd.yml", "-r",
"--mermaid-cmd=mmdc -i output/{}.mmd -o output/{}.svg"};
std::ostringstream ostr;
cli_handler cli{ostr, make_sstream_logger(ostr)};
auto res = cli.handle_options(argv.size(), argv.data());
REQUIRE(res == cli_flow_t::kContinue);
REQUIRE(contains(cli.get_runtime_config().output_directory, "output"));
REQUIRE(cli.get_runtime_config().render_diagrams);
REQUIRE(cli.config.diagrams.at("class_main")->puml().cmd ==
"plantuml -tsvg output/{}.puml");
REQUIRE(cli.config.diagrams.at("class_main")->mermaid().cmd ==
"mmdc -i output/{}.mmd -o output/{}.svg");
REQUIRE(cli.config.diagrams.at("class_main")->puml().after.at(0) ==
"' test comment");
}

View File

@@ -1,6 +1,5 @@
compilation_database_dir: debug compilation_database_dir: debug
output_directory: output output_directory: output
diagrams: diagrams:
include_test: include_test:
type: include type: include

View File

@@ -0,0 +1,13 @@
compilation_database_dir: debug
output_directory: output
plantuml:
cmd: "plantuml -tsvg output/{}.puml"
diagrams:
class_main:
type: class
glob:
- src/**/*.cc
- src/**/*.h
plantuml:
after:
- "' test comment"