0.5.4
C++ to UML diagram generator based on Clang
Loading...
Searching...
No Matches
class_diagram_generator.cc
Go to the documentation of this file.
1/**
2 * @file src/class_diagram/generators/mermaid/class_diagram_generator.cc
3 *
4 * Copyright (c) 2021-2024 Bartek Kryza <bkryza@gmail.com>
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
20
21#include "util/error.h"
22
23#include <inja/inja.hpp>
24
26
31
34 , together_group_stack_{true}
35{
36}
37
38void generator::generate_diagram_type(std::ostream &ostr) const
39{
40 ostr << "classDiagram\n";
41}
42
44 const common::model::element &c, std::ostream &ostr) const
45{
46 const auto full_name = c.full_name(true);
47
48 assert(!full_name.empty());
49
50 print_debug(c, ostr);
51
52 auto class_label = config().simplify_template_type(render_name(full_name));
53
54 ostr << indent(1) << "class " << c.alias() << "[\""
55 << escape_name(class_label) << "\"]\n";
56
57 // Register the added alias
58 m_generated_aliases.emplace(c.alias());
59}
60
61void generator::generate(const class_ &c, std::ostream &ostr) const
62{
63 std::string class_type{"class"};
64
65 ostr << indent(1) << "class " << c.alias();
66
67 ostr << " {" << '\n';
68
69 if (c.is_union())
70 ostr << indent(2) << "<<union>>\n";
71 else if (c.is_abstract())
72 ostr << indent(2) << "<<abstract>>\n";
73
74 //
75 // Process methods
76 //
77 if (config().group_methods()) {
79 }
80 else {
81 generate_methods(c.methods(), ostr);
82 }
83
84 //
85 // Process relationships - here only generate the set of
86 // rendered_relationships we'll generate them in a seperate method
87 //
88 std::set<std::string> rendered_relations;
89
90 std::stringstream all_relations_str;
91 for (const auto &r : c.relationships()) {
92 try {
93 generate_relationship(r, rendered_relations);
94 }
95 catch (error::uml_alias_missing &e) {
96 LOG_DBG("Skipping {} relation from {} to {} due "
97 "to: {}",
98 to_string(r.type()), c.full_name(), r.destination(), e.what());
99 }
100 }
101
102 //
103 // Process members
104 //
105 std::vector<clanguml::class_diagram::model::class_member> members{
106 c.members()};
107
108 sort_class_elements(members);
109
110 for (const auto &m : members) {
111 if (!config().include_relations_also_as_members() &&
112 rendered_relations.find(m.name()) != rendered_relations.end())
113 continue;
114
115 generate_member(m, ostr);
116
117 ostr << '\n';
118 }
119
120 ostr << indent(1) << "}" << '\n';
121
122 if (config().generate_links) {
124 }
125
126 generate_notes(ostr, c);
127
128 for (const auto &member : c.members())
129 generate_member_notes(ostr, member, c.alias());
130
131 for (const auto &method : c.methods())
132 generate_member_notes(ostr, method, c.alias());
133}
134
136 const method_groups_t &methods, std::ostream &ostr) const
137{
138 for (const auto &group : method_groups_) {
139 const auto &group_methods = methods.at(group);
140 if (!group_methods.empty()) {
142 }
143 }
144}
145
147 const std::vector<class_method> &methods, std::ostream &ostr) const
148{
149 auto sorted_methods = methods;
150 sort_class_elements(sorted_methods);
151
152 for (const auto &m : sorted_methods) {
153 generate_method(m, ostr);
154 ostr << '\n';
155 }
156}
157
159 const std::vector<class_method> &methods) const
160{
161 std::map<std::string, std::vector<class_method>> result;
162
163 for (const auto &g : method_groups_) {
164 result[g] = {};
165 }
166
167 for (const auto &m : methods) {
168 if (m.is_constructor() || m.is_destructor()) {
169 result["constructors"].push_back(m);
170 }
171 else if (m.is_copy_assignment() || m.is_move_assignment()) {
172 result["assignment"].push_back(m);
173 }
174 else if (m.is_operator()) {
175 result["operators"].push_back(m);
176 }
177 else {
178 result["other"].push_back(m);
179 }
180 }
181
182 return result;
183}
184
186 const class_diagram::model::class_method &m, std::ostream &ostr) const
187{
188 namespace mermaid_common = clanguml::common::generators::mermaid;
189 const auto &uns = config().using_namespace();
190
191 constexpr auto kAbbreviatedMethodArgumentsLength{15};
192
193 print_debug(m, ostr);
194
195 std::string type{uns.relative(config().simplify_template_type(m.type()))};
196
197 ostr << indent(2) << mermaid_common::to_mermaid(m.access()) << m.name();
198
199 if (!m.template_params().empty()) {
200 m.render_template_params(ostr, config().using_namespace(), false);
201 }
202
203 ostr << "(";
204 if (config().generate_method_arguments() !=
206 std::vector<std::string> params;
207 std::transform(m.parameters().cbegin(), m.parameters().cend(),
208 std::back_inserter(params), [this](const auto &mp) {
209 return config().simplify_template_type(
210 mp.to_string(config().using_namespace()));
211 });
212 auto args_string = fmt::format("{}", fmt::join(params, ", "));
213 if (config().generate_method_arguments() ==
215 args_string = clanguml::util::abbreviate(
216 args_string, kAbbreviatedMethodArgumentsLength);
217 }
218 ostr << args_string;
219 }
220 ostr << ")";
221
222 ostr << " : ";
223
224 std::vector<std::string> method_mods;
225 if (m.is_defaulted()) {
226 method_mods.emplace_back("default");
227 }
228 if (m.is_const()) {
229 method_mods.emplace_back("const");
230 }
231 if (m.is_constexpr()) {
232 method_mods.emplace_back("constexpr");
233 }
234 if (m.is_consteval()) {
235 method_mods.emplace_back("consteval");
236 }
237 if (m.is_coroutine()) {
238 method_mods.emplace_back("coroutine");
239 }
240
241 if (!method_mods.empty()) {
242 ostr << fmt::format("[{}] ", fmt::join(method_mods, ","));
243 }
244
245 ostr << escape_name(render_name(type));
246
247 if (m.is_pure_virtual())
248 ostr << "*";
249
250 if (m.is_static())
251 ostr << "$";
252}
253
255 const class_diagram::model::class_member &m, std::ostream &ostr) const
256{
257 namespace mermaid_common = clanguml::common::generators::mermaid;
258 const auto &uns = config().using_namespace();
259
260 print_debug(m, ostr);
261
262 ostr << indent(2) << mermaid_common::to_mermaid(m.access()) << m.name()
263 << " : "
264 << escape_name(uns.relative(
265 config().simplify_template_type(render_name(m.type()))));
266}
267
268void generator::generate(const concept_ &c, std::ostream &ostr) const
269{
270 ostr << indent(1) << "class"
271 << " " << c.alias();
272
273 ostr << " {" << '\n';
274 ostr << indent(2) << "<<concept>>\n";
275
276 if (config().generate_concept_requirements() &&
277 (c.requires_parameters().size() + c.requires_statements().size() > 0)) {
278 std::vector<std::string> parameters;
279 parameters.reserve(c.requires_parameters().size());
280 for (const auto &p : c.requires_parameters()) {
281 parameters.emplace_back(
282 escape_name(p.to_string(config().using_namespace())));
283 }
284
285 ostr << indent(2)
286 << fmt::format("\"({})\"\n", fmt::join(parameters, ","));
287
288 for (const auto &req : c.requires_statements()) {
289 ostr << indent(2)
290 << fmt::format("\"{}\"\n", escape_name(req, false));
291 }
292 }
293
294 ostr << indent(1) << "}" << '\n';
295
296 if (config().generate_links) {
298 }
299}
300
301void generator::generate_member_notes(std::ostream &ostr,
302 const class_element &member, const std::string &alias) const
303{
304 for (const auto &decorator : member.decorators()) {
305 auto note = std::dynamic_pointer_cast<decorators::note>(decorator);
306 if (note && note->applies_to_diagram(config().name)) {
307 ostr << indent(1) << "note for " << alias << " \"" << note->text
308 << "\"" << '\n';
309 }
310 }
311}
312
313void generator::generate_relationships(std::ostream &ostr) const
314{
315 for (const auto &p : model()) {
316 if (auto *pkg = dynamic_cast<package *>(p.get()); pkg) {
317 generate_relationships(*pkg, ostr);
318 }
319 else if (auto *cls = dynamic_cast<class_ *>(p.get()); cls) {
320 generate_relationships(*cls, ostr);
321 }
322 else if (auto *enm = dynamic_cast<enum_ *>(p.get()); enm) {
323 generate_relationships(*enm, ostr);
324 }
325 else if (auto *cpt = dynamic_cast<concept_ *>(p.get()); cpt) {
326 generate_relationships(*cpt, ostr);
327 }
328 }
329}
330
332 const relationship &r, std::set<std::string> &rendered_relations) const
333{
334 namespace mermaid_common = clanguml::common::generators::mermaid;
335
336 LOG_DBG("Processing relationship {}", to_string(r.type()));
337
338 std::string destination;
339
340 auto target_element = model().get(r.destination());
341 if (!target_element.has_value())
342 throw error::uml_alias_missing{fmt::format(
343 "Missing element in the model for ID: {}", r.destination())};
344
345 destination = target_element.value().full_name(false);
346
347 if (util::starts_with(destination, std::string{"::"}))
348 destination = destination.substr(2, destination.size());
349
350 std::string mmd_relation;
351 if (!r.multiplicity_source().empty())
352 mmd_relation += "\"" + r.multiplicity_source() + "\" ";
353
354 mmd_relation += mermaid_common::to_mermaid(r.type());
355
356 if (!r.multiplicity_destination().empty())
357 mmd_relation += " \"" + r.multiplicity_destination() + "\"";
358
359 if (!r.label().empty()) {
360 if (r.type() == relationship_t::kFriendship)
361 rendered_relations.emplace(fmt::format(
362 "{}[friend]", mermaid_common::to_mermaid(r.access())));
363 else
364 rendered_relations.emplace(r.label());
365 }
366}
367
369 const class_ &c, std::ostream &ostr) const
370{
371 namespace mermaid_common = clanguml::common::generators::mermaid;
372
373 //
374 // Process relationships
375 //
376 std::set<std::string> rendered_relations;
377
378 std::stringstream all_relations_str;
379 std::set<std::string> unique_relations;
380
381 for (const auto &r : c.relationships()) {
382 LOG_DBG("== Processing relationship {}", to_string(r.type()));
383
384 std::stringstream relstr;
385 eid_t destination{};
386 try {
387 destination = r.destination();
388
389 std::string relation_str;
390
391 if (!r.multiplicity_source().empty())
392 relation_str += "\"" + r.multiplicity_source() + "\" ";
393
394 relation_str += mermaid_common::to_mermaid(r.type());
395
396 if (!r.multiplicity_destination().empty())
397 relation_str += " \"" + r.multiplicity_destination() + "\"";
398
399 std::string target_alias;
400 try {
401 target_alias = model().to_alias(destination);
402 }
403 catch (...) {
404 LOG_DBG("Failed to find alias to {}", destination);
405 continue;
406 }
407
408 if (m_generated_aliases.find(target_alias) ==
410 continue;
411
412 if (r.type() == relationship_t::kContainment) {
413 relstr << indent(1) << target_alias << " " << relation_str
414 << " " << c.alias();
415 }
416 else {
417 relstr << indent(1) << c.alias() << " " << relation_str << " "
418 << target_alias;
419 }
420
421 relstr << " : ";
422
423 if (!r.label().empty()) {
424 auto lbl = r.label();
425 if (r.type() == relationship_t::kFriendship)
426 lbl = "[friend]";
427 relstr << mermaid_common::to_mermaid(r.access()) << lbl;
428 rendered_relations.emplace(r.label());
429 }
430
431 if (unique_relations.count(relstr.str()) == 0) {
432 unique_relations.emplace(relstr.str());
433
434 relstr << '\n';
435
436 LOG_DBG("=== Adding relation {}", relstr.str());
437
438 all_relations_str << relstr.str();
439 }
440 }
441 catch (error::uml_alias_missing &e) {
442 LOG_DBG("=== Skipping {} relation from {} to {} due "
443 "to: {}",
444 to_string(r.type()), c.full_name(), destination, e.what());
445 }
446 }
447
448 if (model().should_include(relationship_t::kExtension)) {
449 for (const auto &b : c.parents()) {
450 std::stringstream relstr;
451 try {
452 auto target_alias = model().to_alias(b.id());
453
454 if (m_generated_aliases.find(target_alias) ==
456 continue;
457
458 relstr << indent(1) << target_alias << " <|-- " << c.alias()
459 << '\n';
460 all_relations_str << relstr.str();
461 }
462 catch (error::uml_alias_missing &e) {
463 LOG_DBG("=== Skipping inheritance relation from {} to {} due "
464 "to: {}",
465 b.name(), c.name(), e.what());
466 }
467 }
468 }
469
470 ostr << all_relations_str.str();
471}
472
474 const concept_ &c, std::ostream &ostr) const
475{
476 namespace mermaid_common = clanguml::common::generators::mermaid;
477
478 //
479 // Process relationships
480 //
481 std::set<std::string> rendered_relations;
482
483 std::stringstream all_relations_str;
484 std::set<std::string> unique_relations;
485
486 for (const auto &r : c.relationships()) {
487 LOG_DBG("== Processing relationship {}", to_string(r.type()));
488
489 std::stringstream relstr;
490 eid_t destination{};
491 try {
492 destination = r.destination();
493
494 std::string mmd_relation;
495 if (!r.multiplicity_source().empty())
496 mmd_relation += "\"" + r.multiplicity_source() + "\" ";
497
498 mmd_relation += mermaid_common::to_mermaid(r.type());
499
500 if (!r.multiplicity_destination().empty())
501 mmd_relation += " \"" + r.multiplicity_destination() + "\"";
502
503 std::string target_alias;
504 try {
505 target_alias = model().to_alias(destination);
506 }
507 catch (...) {
508 LOG_DBG("Failed to find alias to {}", destination);
509 continue;
510 }
511
512 if (m_generated_aliases.find(target_alias) ==
514 continue;
515
516 if (r.type() == relationship_t::kContainment) {
517 relstr << indent(1) << target_alias << " " << mmd_relation
518 << " " << c.alias();
519 }
520 else {
521 relstr << indent(1) << c.alias() << " " << mmd_relation << " "
522 << target_alias;
523 }
524
525 relstr << " : ";
526
527 if (!r.label().empty()) {
528 auto lbl = r.label();
529 if (r.type() == relationship_t::kFriendship)
530 lbl = "[friend]";
531 relstr << mermaid_common::to_mermaid(r.access()) << lbl;
532 rendered_relations.emplace(r.label());
533 }
534
535 if (unique_relations.count(relstr.str()) == 0) {
536 unique_relations.emplace(relstr.str());
537
538 relstr << '\n';
539
540 LOG_DBG("=== Adding relation {}", relstr.str());
541
542 all_relations_str << relstr.str();
543 }
544 }
545 catch (error::uml_alias_missing &e) {
546 LOG_DBG("=== Skipping {} relation from {} to {} due "
547 "to: {}",
548 to_string(r.type()), c.full_name(), destination, e.what());
549 }
550 }
551
552 ostr << all_relations_str.str();
553}
554
555void generator::generate_relationships(const enum_ &e, std::ostream &ostr) const
556{
557 for (const auto &r : e.relationships()) {
558 eid_t destination{};
559 std::stringstream relstr;
560 try {
561 destination = r.destination();
562
563 auto target_alias = model().to_alias(destination);
564
565 if (m_generated_aliases.find(target_alias) ==
567 continue;
568
569 if (r.type() == relationship_t::kContainment) {
570 relstr << indent(1) << target_alias << " "
572 r.type())
573 << " " << e.alias();
574 }
575 else {
576 relstr << indent(1) << e.alias() << " "
578 r.type())
579 << " " << target_alias;
580 }
581
582 auto lbl = r.label();
583 if (r.type() == relationship_t::kFriendship)
584 lbl = "[friend]";
585
586 relstr << " : " << r.label();
587
588 relstr << '\n';
589
590 ostr << relstr.str();
591 }
592 catch (error::uml_alias_missing &ex) {
593 LOG_DBG("Skipping {} relation from {} to {} due "
594 "to: {}",
596 e.full_name(), destination, ex.what());
597 }
598 }
599}
600
601void generator::generate(const enum_ &e, std::ostream &ostr) const
602{
603 ostr << indent(1) << "class " << e.alias();
604
605 ostr << " {" << '\n';
606
607 ostr << indent(2) << "<<enumeration>>\n";
608
609 for (const auto &enum_constant : e.constants()) {
610 ostr << indent(2) << enum_constant << '\n';
611 }
612
613 ostr << indent(1) << "}" << '\n';
614
615 if (config().generate_links) {
617 }
618
619 generate_notes(ostr, e);
620}
621
622void generator::generate(const package &p, std::ostream &ostr) const
623{
624 for (const auto &subpackage : p) {
625 if (dynamic_cast<package *>(subpackage.get()) != nullptr) {
626 // TODO: add option - generate_empty_packages
627 const auto &sp = dynamic_cast<package &>(*subpackage);
628 if (!sp.is_empty()) {
629 together_group_stack_.enter();
630
631 generate(sp, ostr);
632
633 together_group_stack_.leave();
634 }
635 }
636 else if (auto *cls = dynamic_cast<class_ *>(subpackage.get()); cls) {
637 if (model().should_include(*subpackage)) {
638 auto together_group =
639 config().get_together_group(cls->full_name(false));
640 if (together_group) {
641 together_group_stack_.group_together(
642 together_group.value(), cls);
643 }
644 else {
645 generate_alias(*cls, ostr);
646 generate(*cls, ostr);
647 }
648 }
649 }
650 else if (auto *enm = dynamic_cast<enum_ *>(subpackage.get()); enm) {
651 if (model().should_include(*subpackage)) {
652 auto together_group =
653 config().get_together_group(subpackage->full_name(false));
654 if (together_group) {
655 together_group_stack_.group_together(
656 together_group.value(), enm);
657 }
658 else {
659 generate_alias(*enm, ostr);
660 generate(*enm, ostr);
661 }
662 }
663 }
664 else if (auto *cpt = dynamic_cast<concept_ *>(subpackage.get()); cpt) {
665 if (model().should_include(*subpackage)) {
666 auto together_group =
667 config().get_together_group(cpt->full_name(false));
668 if (together_group) {
669 together_group_stack_.group_together(
670 together_group.value(), cpt);
671 }
672 else {
673 generate_alias(*cpt, ostr);
674 generate(*cpt, ostr);
675 }
676 }
677 }
678 }
679}
680
682 const package &p, std::ostream &ostr) const
683{
684 for (const auto &subpackage : p) {
685 if (dynamic_cast<package *>(subpackage.get()) != nullptr) {
686 // TODO: add option - generate_empty_packages, currently
687 // packages which do not contain anything but other
688 // packages are skipped
689 const auto &sp = dynamic_cast<package &>(*subpackage);
690 if (!sp.is_empty())
691 generate_relationships(sp, ostr);
692 }
693 else if (dynamic_cast<class_ *>(subpackage.get()) != nullptr) {
694 if (model().should_include(*subpackage)) {
696 dynamic_cast<class_ &>(*subpackage), ostr);
697 }
698 }
699 else if (dynamic_cast<enum_ *>(subpackage.get()) != nullptr) {
700 if (model().should_include(*subpackage)) {
702 dynamic_cast<enum_ &>(*subpackage), ostr);
703 }
704 }
705 else if (dynamic_cast<concept_ *>(subpackage.get()) != nullptr) {
706 if (model().should_include(*subpackage)) {
708 dynamic_cast<concept_ &>(*subpackage), ostr);
709 }
710 }
711 }
712}
713
714void generator::generate_diagram(std::ostream &ostr) const
715{
717
718 generate_groups(ostr);
719
721}
722
723void generator::generate_top_level_elements(std::ostream &ostr) const
724{
725 for (const auto &p : model()) {
726 if (auto *pkg = dynamic_cast<package *>(p.get()); pkg) {
727 if (!pkg->is_empty())
728 generate(*pkg, ostr);
729 }
730 else if (auto *cls = dynamic_cast<class_ *>(p.get()); cls) {
731 auto together_group =
732 config().get_together_group(cls->full_name(false));
733 if (together_group) {
734 together_group_stack_.group_together(
735 together_group.value(), cls);
736 }
737 else {
738 generate_alias(*cls, ostr);
739 generate(*cls, ostr);
740 }
741 }
742 else if (auto *enm = dynamic_cast<enum_ *>(p.get()); enm) {
743 auto together_group =
744 config().get_together_group(enm->full_name(false));
745 if (together_group) {
746 together_group_stack_.group_together(
747 together_group.value(), enm);
748 }
749 else {
750 generate_alias(*enm, ostr);
751 generate(*enm, ostr);
752 }
753 }
754 else if (auto *cpt = dynamic_cast<concept_ *>(p.get()); cpt) {
755 auto together_group =
756 config().get_together_group(cpt->full_name(false));
757 if (together_group) {
758 together_group_stack_.group_together(
759 together_group.value(), cpt);
760 }
761 else {
762 generate_alias(*cpt, ostr);
763 generate(*cpt, ostr);
764 }
765 }
766 }
767}
768
769void generator::generate_groups(std::ostream &ostr) const
770{
771 for (const auto &[group_name, group_elements] :
772 together_group_stack_.get_current_groups()) {
773
774 for (auto *e : group_elements) {
775 if (auto *cls = dynamic_cast<class_ *>(e); cls) {
776 generate_alias(*cls, ostr);
777 generate(*cls, ostr);
778 }
779 if (auto *enm = dynamic_cast<enum_ *>(e); enm) {
780 generate_alias(*enm, ostr);
781 generate(*enm, ostr);
782 }
783 if (auto *cpt = dynamic_cast<concept_ *>(e); cpt) {
784 generate_alias(*cpt, ostr);
785 generate(*cpt, ostr);
786 }
787 }
788 }
789}
790
791} // namespace clanguml::class_diagram::generators::mermaid