Implement CLI options for adding diagrams to config from templates

This commit is contained in:
Bartek Kryza
2023-03-08 01:25:20 +01:00
parent 2092a0e3e6
commit 41537c5401
4 changed files with 180 additions and 57 deletions

View File

@@ -553,12 +553,20 @@ template <> struct convert<diagram_template> {
if (node.Type() == NodeType::Scalar) {
// Check that the template provided as string is at least valid YAML
const auto yaml_node = Load(node.as<std::string>());
const auto diagram_type = yaml_node["type"].as<std::string>();
const auto template_root_it = yaml_node.begin();
const auto diagram_name_template =
template_root_it->first.as<std::string>();
const auto diagram_type =
template_root_it->second["type"].as<std::string>();
rhs.type = clanguml::common::model::from_string(diagram_type);
rhs.jinja_template = Dump(yaml_node);
}
else {
const auto diagram_type = node["type"].as<std::string>();
const auto template_root_it = node.begin();
const auto diagram_name_template =
template_root_it->first.as<std::string>();
const auto diagram_type =
template_root_it->second["type"].as<std::string>();
rhs.type = clanguml::common::model::from_string(diagram_type);
rhs.jinja_template = Dump(node);
}

View File

@@ -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<std::string> &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<std::string> add_sequence_diagram;
std::optional<std::string> add_package_diagram;
std::optional<std::string> add_include_diagram;
std::optional<std::string> add_diagram_from_template;
bool dump_config{false};
std::optional<bool> paths_relative_to_pwd{};
std::vector<std::string> 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<std::string> &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<std::string>();
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;

View File

@@ -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);

View File

@@ -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