diff --git a/src/config/yaml_decoders.cc b/src/config/yaml_decoders.cc index 5fa98f11..d394b35d 100644 --- a/src/config/yaml_decoders.cc +++ b/src/config/yaml_decoders.cc @@ -553,12 +553,20 @@ template <> struct convert { if (node.Type() == NodeType::Scalar) { // Check that the template provided as string is at least valid YAML const auto yaml_node = Load(node.as()); - const auto diagram_type = yaml_node["type"].as(); + const auto template_root_it = yaml_node.begin(); + const auto diagram_name_template = + template_root_it->first.as(); + const auto diagram_type = + template_root_it->second["type"].as(); rhs.type = clanguml::common::model::from_string(diagram_type); rhs.jinja_template = Dump(yaml_node); } else { - const auto diagram_type = node["type"].as(); + const auto template_root_it = node.begin(); + const auto diagram_name_template = + template_root_it->first.as(); + const auto diagram_type = + template_root_it->second["type"].as(); rhs.type = clanguml::common::model::from_string(diagram_type); rhs.jinja_template = Dump(node); } diff --git a/src/main.cc b/src/main.cc index 8c730cc0..d5824101 100644 --- a/src/main.cc +++ b/src/main.cc @@ -62,6 +62,14 @@ void print_version(); */ void print_diagrams_list(const clanguml::config::config &cfg); +/** + * Print list of available diagram templates, including their names + * and types. + * + * @param cfg + */ +void print_diagram_templates(const clanguml::config::config &cfg); + /** * Print effective config after loading and setting default values. * @@ -87,6 +95,18 @@ int create_config_file(); int add_config_diagram(clanguml::common::model::diagram_t type, const std::string &config_file_path, const std::string &name); +/** + * Add diagram based on template + * @param config_file_path + * @param cfg + * @param template_name + * @param template_variables + * @return + */ +int add_config_diagram_from_template(const std::string &config_file_path, + const config::config &cfg, const std::string &template_name, + const std::vector &template_variables); + /** * Check if diagram output directory exists, if not create it * @@ -172,8 +192,11 @@ int main(int argc, const char *argv[]) std::optional add_sequence_diagram; std::optional add_package_diagram; std::optional add_include_diagram; + std::optional add_diagram_from_template; bool dump_config{false}; std::optional paths_relative_to_pwd{}; + std::vector template_variables{}; + bool list_templates{false}; app.add_option("-c,--config", config_path, "Location of configuration file, when '-' read from stdin"); @@ -200,6 +223,12 @@ int main(int argc, const char *argv[]) "Add package diagram config"); app.add_option("--add-include-diagram", add_include_diagram, "Add include diagram config"); + app.add_option("--add-diagram-from-template", add_diagram_from_template, + "Add diagram config based on diagram template"); + app.add_option("--template-variable", template_variables, + "Specify a value for a template variable"); + app.add_flag("--list-templates", list_templates, + "List all available diagram templates"); app.add_flag( "--dump-config", dump_config, "Print effective config to stdout"); app.add_flag("--paths-relative-to-pwd", paths_relative_to_pwd, @@ -221,8 +250,8 @@ int main(int argc, const char *argv[]) } if ((config_path == "-") && - (initialize || add_class_diagram.has_value() || - add_sequence_diagram.has_value() || + (initialize || add_diagram_from_template || + add_class_diagram.has_value() || add_sequence_diagram.has_value() || add_package_diagram.has_value() || add_include_diagram.has_value())) { @@ -276,6 +305,16 @@ int main(int argc, const char *argv[]) return 0; } + if (list_templates) { + print_diagram_templates(config); + return 0; + } + + if (config_path != "-" && add_diagram_from_template) { + return add_config_diagram_from_template(config_path, config, + add_diagram_from_template.value(), template_variables); + } + LOG_INFO("Loaded clang-uml config from {}", config_path); // @@ -539,6 +578,23 @@ void print_diagrams_list(const clanguml::config::config &cfg) } } +void print_diagram_templates(const clanguml::config::config &cfg) +{ + using std::cout; + + if (!cfg.diagram_templates) { + cout << "No diagram templates are defined in the config file\n"; + return; + } + + cout << "The following diagram templates are available:\n"; + for (const auto &[name, diagram_template] : cfg.diagram_templates()) { + cout << " - " << name << " [" << to_string(diagram_template.type) + << "]"; + cout << '\n'; + } +} + int create_config_file() { namespace fs = std::filesystem; @@ -669,6 +725,65 @@ int add_config_diagram(clanguml::common::model::diagram_t type, return 0; } +int add_config_diagram_from_template(const std::string &config_file_path, + const config::config &cfg, const std::string &template_name, + const std::vector &template_variables) +{ + if (!cfg.diagram_templates || + !(cfg.diagram_templates().find(template_name) != + cfg.diagram_templates().end())) { + std::cerr << "ERROR: No such diagram template: " << template_name + << "\n"; + return 1; + } + + // First, try to render the template using inja and create a YAML node from + // it + inja::json ctx; + for (const auto &tv : template_variables) { + const auto var = util::split(tv, "="); + if (var.size() != 2) { + std::cerr << "ERROR: Invalid template variable " << tv << "\n"; + return 1; + } + + ctx[var.at(0)] = var.at(1); + } + + auto diagram_template_str = + cfg.diagram_templates().at(template_name).jinja_template; + + auto diagram_str = inja::render(diagram_template_str, ctx); + + auto diagram_node = YAML::Load(diagram_str); + + namespace fs = std::filesystem; + + fs::path config_file{config_file_path}; + + if (!fs::exists(config_file)) { + std::cerr << "ERROR: " << config_file_path << " file doesn't exists\n"; + return 1; + } + + YAML::Node doc = YAML::LoadFile(config_file.string()); + + const auto diagram_name = diagram_node.begin()->first.as(); + doc["diagrams"][diagram_name] = diagram_node.begin()->second; + + YAML::Emitter out; + out.SetIndent(2); + + out << doc; + out << YAML::Newline; + + std::ofstream ofs(config_file); + ofs << out.c_str(); + ofs.close(); + + return 0; +} + void print_config(const clanguml::config::config &cfg) { YAML::Emitter out; diff --git a/tests/test_config.cc b/tests/test_config.cc index 2759c706..a3f92441 100644 --- a/tests/test_config.cc +++ b/tests/test_config.cc @@ -270,34 +270,34 @@ TEST_CASE("Test config diagram_templates", "[unit-test]") REQUIRE(cfg.diagram_templates()["bases_hierarchy_tmpl"].type == clanguml::common::model::diagram_t::kClass); REQUIRE(cfg.diagram_templates()["bases_hierarchy_tmpl"].jinja_template == - R"(name: "{{ class_name }}_parents_hierarchy" -type: class -include: - parents: "{{ class_name }}" - namespaces: "{{ namespace_name }}" -relationships: - - inheritance -exclude: - access: [public, protected, private] -plantuml: - before: - - left to right direction)"); + R"("{{ class_name }}_parents_hierarchy": + type: class + include: + parents: "{{ class_name }}" + namespaces: "{{ namespace_name }}" + relationships: + - inheritance + exclude: + access: [public, protected, private] + plantuml: + before: + - left to right direction)"); REQUIRE(cfg.diagram_templates()["children_hierarchy_tmpl"].type == clanguml::common::model::diagram_t::kClass); REQUIRE(cfg.diagram_templates()["children_hierarchy_tmpl"].jinja_template == - R"(name: "{{ class_name }}_children_hierarchy" -type: class -include: - subclasses: "{{ class_name }}" - namespaces: "{{ namespace_name }}" -relationships: - - inheritance -exclude: - access: [public, protected, private] -plantuml: - before: - - left to right direction)"); + R"("{{ class_name }}_children_hierarchy": + type: class + include: + subclasses: "{{ class_name }}" + namespaces: "{{ namespace_name }}" + relationships: + - inheritance + exclude: + access: [public, protected, private] + plantuml: + before: + - left to right direction)"); REQUIRE(cfg.diagram_templates()["main_sequence_tmpl"].type == clanguml::common::model::diagram_t::kSequence); diff --git a/tests/test_config_data/diagram_templates.yml b/tests/test_config_data/diagram_templates.yml index 5d442233..3c877b88 100644 --- a/tests/test_config_data/diagram_templates.yml +++ b/tests/test_config_data/diagram_templates.yml @@ -3,37 +3,37 @@ output_directory: output diagram_templates: bases_hierarchy_tmpl: - name: '{{ class_name }}_parents_hierarchy' - type: class - include: - parents: '{{ class_name }}' - namespaces: '{{ namespace_name }}' - relationships: - - inheritance - exclude: - access: [public, protected, private] - plantuml: - before: - - left to right direction + '{{ class_name }}_parents_hierarchy': + type: class + include: + parents: '{{ class_name }}' + namespaces: '{{ namespace_name }}' + relationships: + - inheritance + exclude: + access: [public, protected, private] + plantuml: + before: + - left to right direction children_hierarchy_tmpl: | - name: '{{ class_name }}_children_hierarchy' - type: class - include: - subclasses: '{{ class_name }}' - namespaces: '{{ namespace_name }}' - relationships: - - inheritance - exclude: - access: [public, protected, private] - plantuml: - before: - - left to right direction + '{{ class_name }}_children_hierarchy': + type: class + include: + subclasses: '{{ class_name }}' + namespaces: '{{ namespace_name }}' + relationships: + - inheritance + exclude: + access: [public, protected, private] + plantuml: + before: + - left to right direction main_sequence_tmpl: | - name: main_sequence_diargam - type: sequence - glob: [ {{ }} ] - start_from: - - function: 'main(int,const char**)' + main_sequence_diagram: + type: sequence + glob: [ {{ glob }} ] + start_from: + - function: 'main(int,const char**)' diagrams: diagram1: type: class \ No newline at end of file