0.6.0
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-2025 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
35
36{
37}
38
39void generator::generate_diagram_type(std::ostream &ostr) const
40{
41 ostr << "classDiagram\n";
42}
43
45 const common::model::element &c, std::ostream &ostr) const
46{
47 const auto full_name = display_name_adapter(c).full_name(true);
48
49 assert(!full_name.empty());
50
51 print_debug(c, ostr);
52
53 auto class_label = config().simplify_template_type(full_name);
54
55 ostr << indent(1) << "class " << c.alias() << "[\""
56 << escape_name(class_label) << "\"]\n";
57
58 // Register the added alias
59 m_generated_aliases.emplace(c.alias());
60}
61
62void generator::generate(const class_ &c, std::ostream &ostr) const
63{
64 std::string class_type{"class"};
65
66 ostr << indent(1) << "class " << c.alias();
67
68 ostr << " {" << '\n';
69
70 if (c.is_union())
71 ostr << indent(2) << "<<union>>\n";
72 else if (c.is_abstract())
73 ostr << indent(2) << "<<abstract>>\n";
74
75 //
76 // Process methods
77 //
78 if (config().group_methods()) {
80 }
81 else {
82 generate_methods(c.methods(), ostr);
83 }
84
85 //
86 // Process relationships - here only generate the set of
87 // rendered_relationships we'll generate them in a seperate method
88 //
89 std::set<std::string> rendered_relations;
90
91 std::stringstream all_relations_str;
92 for (const auto &r : c.relationships()) {
93 try {
94 generate_relationship(r, rendered_relations);
95 }
96 catch (error::uml_alias_missing &e) {
97 LOG_DBG("Skipping {} relation from {} to {} due "
98 "to: {}",
99 to_string(r.type()), c.full_name(false), r.destination(),
100 e.what());
101 }
102 }
103
104 //
105 // Process members
106 //
107 std::vector<clanguml::class_diagram::model::class_member> members{
108 c.members()};
109
110 sort_class_elements(members);
111
112 for (const auto &m : members) {
113 if (!config().include_relations_also_as_members() &&
114 rendered_relations.find(m.name()) != rendered_relations.end())
115 continue;
116
117 generate_member(m, ostr);
118
119 ostr << '\n';
120 }
121
122 ostr << indent(1) << "}" << '\n';
123
124 if (config().generate_links) {
126 }
127
128 generate_notes(ostr, c);
129
130 for (const auto &member : c.members())
131 generate_member_notes(ostr, member, c.alias());
132
133 for (const auto &method : c.methods())
134 generate_member_notes(ostr, method, c.alias());
135}
136
137void generator::start_method_group(std::ostream &ostr) const { }
138
140 const std::vector<class_method> &methods, std::ostream &ostr) const
141{
142 auto sorted_methods = methods;
143 sort_class_elements(sorted_methods);
144
145 for (const auto &m : sorted_methods) {
146 generate_method(m, ostr);
147 ostr << '\n';
148 }
149}
150
152 const std::vector<objc_method> &methods, std::ostream &ostr) const
153{
154 auto sorted_methods = methods;
155 sort_class_elements(sorted_methods);
156
157 for (const auto &m : sorted_methods) {
158 generate_method(m, ostr);
159 ostr << '\n';
160 }
161}
162
164 const class_diagram::model::class_method &m, std::ostream &ostr) const
165{
166 namespace mermaid_common = clanguml::common::generators::mermaid;
167 const auto &uns = config().using_namespace();
168
169 constexpr auto kAbbreviatedMethodArgumentsLength{15};
170
171 print_debug(m, ostr);
172
173 std::string type{uns.relative(
174 config().simplify_template_type(display_name_adapter(m).type()))};
175
176 ostr << indent(2) << mermaid_common::to_mermaid(m.access()) << m.name();
177
178 if (!m.template_params().empty()) {
179 m.render_template_params(ostr, config().using_namespace(), false);
180 }
181
182 ostr << "(";
183 if (config().generate_method_arguments() !=
185 std::vector<std::string> params;
186 std::transform(m.parameters().cbegin(), m.parameters().cend(),
187 std::back_inserter(params), [this](const auto &mp) {
188 return config().simplify_template_type(
189 mp.to_string(config().using_namespace()));
190 });
191 auto args_string = fmt::format("{}", fmt::join(params, ", "));
192 if (config().generate_method_arguments() ==
194 args_string = clanguml::util::abbreviate(
195 args_string, kAbbreviatedMethodArgumentsLength);
196 }
197 ostr << escape_name(args_string);
198 }
199 ostr << ")";
200
201 ostr << " : ";
202
203 std::vector<std::string> method_mods;
204 if (m.is_defaulted()) {
205 method_mods.emplace_back("default");
206 }
207 if (m.is_const()) {
208 method_mods.emplace_back("const");
209 }
210 if (m.is_constexpr()) {
211 method_mods.emplace_back("constexpr");
212 }
213 if (m.is_consteval()) {
214 method_mods.emplace_back("consteval");
215 }
216 if (m.is_coroutine()) {
217 method_mods.emplace_back("coroutine");
218 }
219
220 if (!method_mods.empty()) {
221 ostr << fmt::format("[{}] ", fmt::join(method_mods, ","));
222 }
223
224 ostr << escape_name(type);
225
226 if (m.is_pure_virtual())
227 ostr << "*";
228
229 if (m.is_static())
230 ostr << "$";
231}
232
234 const class_diagram::model::objc_method &m, std::ostream &ostr) const
235{
236 namespace mermaid_common = clanguml::common::generators::mermaid;
237 const auto &uns = config().using_namespace();
238
239 constexpr auto kAbbreviatedMethodArgumentsLength{15};
240
241 print_debug(m, ostr);
242
243 std::string type{uns.relative(
244 config().simplify_template_type(display_name_adapter(m).type()))};
245
246 ostr << indent(2) << mermaid_common::to_mermaid(m.access()) << m.name();
247
248 ostr << "(";
249 if (config().generate_method_arguments() !=
251 std::vector<std::string> params;
252 std::transform(m.parameters().cbegin(), m.parameters().cend(),
253 std::back_inserter(params), [this](const auto &mp) {
254 return config().simplify_template_type(
255 mp.to_string(config().using_namespace()));
256 });
257 auto args_string = fmt::format("{}", fmt::join(params, ", "));
258 if (config().generate_method_arguments() ==
260 args_string = clanguml::util::abbreviate(
261 args_string, kAbbreviatedMethodArgumentsLength);
262 }
263 ostr << escape_name(args_string);
264 }
265 ostr << ")";
266
267 ostr << " : ";
268
269 std::vector<std::string> method_mods;
270 if (m.is_optional()) {
271 method_mods.emplace_back("default");
272 }
273
274 if (!method_mods.empty()) {
275 ostr << fmt::format("[{}] ", fmt::join(method_mods, ","));
276 }
277
278 ostr << escape_name(type);
279
280 if (m.is_static())
281 ostr << "$";
282}
283
285 const class_diagram::model::class_member &m, std::ostream &ostr) const
286{
287 namespace mermaid_common = clanguml::common::generators::mermaid;
288 const auto &uns = config().using_namespace();
289
290 print_debug(m, ostr);
291
292 ostr << indent(2) << mermaid_common::to_mermaid(m.access()) << m.name()
293 << " : "
294 << escape_name(uns.relative(config().simplify_template_type(
295 display_name_adapter(m).type())));
296}
297
299 const class_diagram::model::objc_member &m, std::ostream &ostr) const
300{
301 namespace mermaid_common = clanguml::common::generators::mermaid;
302 const auto &uns = config().using_namespace();
303
304 print_debug(m, ostr);
305
306 ostr << indent(2) << mermaid_common::to_mermaid(m.access()) << m.name()
307 << " : "
308 << escape_name(uns.relative(config().simplify_template_type(
309 display_name_adapter(m).type())));
310}
311
312void generator::generate(const concept_ &c, std::ostream &ostr) const
313{
314 ostr << indent(1) << "class" << " " << c.alias();
315
316 ostr << " {" << '\n';
317 ostr << indent(2) << "<<concept>>\n";
318
319 if (config().generate_concept_requirements() &&
320 (c.requires_parameters().size() + c.requires_statements().size() > 0)) {
321 std::vector<std::string> parameters;
322 parameters.reserve(c.requires_parameters().size());
323 for (const auto &p : c.requires_parameters()) {
324 parameters.emplace_back(
325 escape_name(p.to_string(config().using_namespace())));
326 }
327
328 ostr << indent(2)
329 << fmt::format("\"({})\"\n", fmt::join(parameters, ","));
330
331 for (const auto &req : c.requires_statements()) {
332 ostr << indent(2)
333 << fmt::format("\"{}\"\n", escape_name(req, false));
334 }
335 }
336
337 ostr << indent(1) << "}" << '\n';
338
339 if (config().generate_links) {
341 }
342}
343
344void generator::generate_member_notes(std::ostream &ostr,
345 const class_element &member, const std::string &alias) const
346{
347 for (const auto &decorator : member.decorators()) {
348 auto note = std::dynamic_pointer_cast<decorators::note>(decorator);
349 if (note && note->applies_to_diagram(config().name)) {
350 ostr << indent(1) << "note for " << alias << " \"" << note->text
351 << "\"" << '\n';
352 }
353 }
354}
355
357 const relationship &r, std::set<std::string> &rendered_relations) const
358{
359 namespace mermaid_common = clanguml::common::generators::mermaid;
360
361 LOG_DBG("Processing relationship {}", to_string(r.type()));
362
363 std::string destination;
364
365 auto target_element = model().get(r.destination());
366 if (!target_element.has_value())
367 throw error::uml_alias_missing{fmt::format(
368 "Missing element in the model for ID: {}", r.destination())};
369
370 destination = target_element.value().full_name(false);
371
372 if (util::starts_with(destination, std::string{"::"}))
373 destination = destination.substr(2, destination.size());
374
375 std::string mmd_relation;
376 if (!r.multiplicity_source().empty())
377 mmd_relation += "\"" + r.multiplicity_source() + "\" ";
378
379 mmd_relation += mermaid_common::to_mermaid(r.type());
380
381 if (!r.multiplicity_destination().empty())
382 mmd_relation += " \"" + r.multiplicity_destination() + "\"";
383
384 if (!r.label().empty()) {
385 if (r.type() == relationship_t::kFriendship)
386 rendered_relations.emplace(fmt::format(
387 "{}[friend]", mermaid_common::to_mermaid(r.access())));
388 else
389 rendered_relations.emplace(r.label());
390 }
391}
392
394 const class_ &c, std::ostream &ostr) const
395{
396 namespace mermaid_common = clanguml::common::generators::mermaid;
397
398 //
399 // Process relationships
400 //
401 std::set<std::string> rendered_relations;
402
403 std::stringstream all_relations_str;
404 std::set<std::string> unique_relations;
405
406 for (const auto &r : c.relationships()) {
407 LOG_DBG("== Processing relationship {}", to_string(r.type()));
408
409 std::stringstream relstr;
410 eid_t destination{};
411 try {
412 destination = r.destination();
413
414 std::string relation_str;
415
416 if (!r.multiplicity_source().empty())
417 relation_str += "\"" + r.multiplicity_source() + "\" ";
418
419 relation_str += mermaid_common::to_mermaid(r.type());
420
421 if (!r.multiplicity_destination().empty())
422 relation_str += " \"" + r.multiplicity_destination() + "\"";
423
424 std::string target_alias;
425 try {
426 target_alias = model().to_alias(destination);
427 }
428 catch (...) {
429 LOG_DBG("Failed to find alias to {}", destination);
430 continue;
431 }
432
433 if (m_generated_aliases.find(target_alias) ==
435 continue;
436
437 if (r.type() == relationship_t::kContainment) {
438 relstr << indent(1) << target_alias << " " << relation_str
439 << " " << c.alias();
440 }
441 else if (r.type() == relationship_t::kExtension) {
442 relstr << indent(1) << target_alias << " <|-- " << c.alias();
443 }
444 else {
445 relstr << indent(1) << c.alias() << " " << relation_str << " "
446 << target_alias;
447 }
448
449 relstr << " : ";
450
451 if (!r.label().empty()) {
452 auto lbl = r.label();
453 if (r.type() == relationship_t::kFriendship)
454 lbl = "[friend]";
455 relstr << mermaid_common::to_mermaid(r.access()) << lbl;
456 rendered_relations.emplace(r.label());
457 }
458
459 if (unique_relations.count(relstr.str()) == 0) {
460 unique_relations.emplace(relstr.str());
461
462 LOG_DBG("=== Adding relation {}", relstr.str());
463
464 all_relations_str << relstr.str() << '\n';
465 }
466 }
467 catch (error::uml_alias_missing &e) {
468 LOG_DBG("=== Skipping {} relation from {} to {} due "
469 "to: {}",
470 to_string(r.type()), c.full_name(true), destination, e.what());
471 }
472 }
473
474 ostr << all_relations_str.str();
475}
476
478 const concept_ &c, std::ostream &ostr) const
479{
480 namespace mermaid_common = clanguml::common::generators::mermaid;
481
482 //
483 // Process relationships
484 //
485 std::set<std::string> rendered_relations;
486
487 std::stringstream all_relations_str;
488 std::set<std::string> unique_relations;
489
490 for (const auto &r : c.relationships()) {
491 LOG_DBG("== Processing relationship {}", to_string(r.type()));
492
493 std::stringstream relstr;
494 eid_t destination{};
495 try {
496 destination = r.destination();
497
498 std::string mmd_relation;
499 if (!r.multiplicity_source().empty())
500 mmd_relation += "\"" + r.multiplicity_source() + "\" ";
501
502 mmd_relation += mermaid_common::to_mermaid(r.type());
503
504 if (!r.multiplicity_destination().empty())
505 mmd_relation += " \"" + r.multiplicity_destination() + "\"";
506
507 std::string target_alias;
508 try {
509 target_alias = model().to_alias(destination);
510 }
511 catch (...) {
512 LOG_DBG("Failed to find alias to {}", destination);
513 continue;
514 }
515
516 if (m_generated_aliases.find(target_alias) ==
518 continue;
519
520 if (r.type() == relationship_t::kContainment) {
521 relstr << indent(1) << target_alias << " " << mmd_relation
522 << " " << c.alias();
523 }
524 else {
525 relstr << indent(1) << c.alias() << " " << mmd_relation << " "
526 << target_alias;
527 }
528
529 relstr << " : ";
530
531 if (!r.label().empty()) {
532 auto lbl = r.label();
533 if (r.type() == relationship_t::kFriendship)
534 lbl = "[friend]";
535 relstr << mermaid_common::to_mermaid(r.access()) << lbl;
536 rendered_relations.emplace(r.label());
537 }
538
539 if (unique_relations.count(relstr.str()) == 0) {
540 unique_relations.emplace(relstr.str());
541
542 LOG_TRACE("=== Adding relation {}", relstr.str());
543
544 all_relations_str << relstr.str() << '\n';
545 }
546 }
547 catch (error::uml_alias_missing &e) {
548 LOG_DBG("=== Skipping {} relation from {} to {} due "
549 "to: {}",
550 to_string(r.type()), c.full_name(true), destination, e.what());
551 }
552 }
553
554 ostr << all_relations_str.str();
555}
556
557void generator::generate_relationships(const enum_ &e, std::ostream &ostr) const
558{
559 for (const auto &r : e.relationships()) {
560 eid_t destination{};
561 std::stringstream relstr;
562 try {
563 destination = r.destination();
564
565 auto target_alias = model().to_alias(destination);
566
567 if (m_generated_aliases.find(target_alias) ==
569 continue;
570
571 if (r.type() == relationship_t::kContainment) {
572 relstr << indent(1) << target_alias << " "
574 r.type())
575 << " " << e.alias();
576 }
577 else {
578 relstr << indent(1) << e.alias() << " "
580 r.type())
581 << " " << target_alias;
582 }
583
584 auto lbl = r.label();
585 if (r.type() == relationship_t::kFriendship)
586 lbl = "[friend]";
587
588 relstr << " : " << r.label();
589
590 relstr << '\n';
591
592 ostr << relstr.str();
593 }
594 catch (error::uml_alias_missing &ex) {
595 LOG_DBG("Skipping {} relation from {} to {} due "
596 "to: {}",
598 e.full_name(true), destination, ex.what());
599 }
600 }
601}
602
603void generator::generate(const enum_ &e, std::ostream &ostr) const
604{
605 ostr << indent(1) << "class " << e.alias();
606
607 ostr << " {" << '\n';
608
609 ostr << indent(2) << "<<enumeration>>\n";
610
611 for (const auto &enum_constant : e.constants()) {
612 ostr << indent(2) << enum_constant << '\n';
613 }
614
615 ostr << indent(1) << "}" << '\n';
616
617 if (config().generate_links) {
619 }
620
621 generate_notes(ostr, e);
622}
623
624void generator::generate(const objc_interface &c, std::ostream &ostr) const
625{
626 std::string class_type{"class"};
627
628 ostr << indent(1) << "class " << c.alias();
629
630 ostr << " {" << '\n';
631
632 if (c.is_protocol())
633 ostr << indent(2) << "<< ObjC Protocol >>\n";
634 else if (c.is_protocol())
635 ostr << indent(2) << "<< ObjC Category >>\n";
636 else
637 ostr << indent(2) << "<< ObjC Interface >>\n";
638
639 //
640 // Process methods
641 //
642 generate_methods(c.methods(), ostr);
643
644 //
645 // Process relationships - here only generate the set of
646 // rendered_relationships we'll generate them in a seperate method
647 //
648 std::set<std::string> rendered_relations;
649
650 std::stringstream all_relations_str;
651 for (const auto &r : c.relationships()) {
652 try {
653 generate_relationship(r, rendered_relations);
654 }
655 catch (error::uml_alias_missing &e) {
656 LOG_DBG("Skipping {} relation from {} to {} due "
657 "to: {}",
658 to_string(r.type()), c.full_name(true), r.destination(),
659 e.what());
660 }
661 }
662
663 //
664 // Process members
665 //
666 std::vector<clanguml::class_diagram::model::objc_member> members{
667 c.members()};
668
669 sort_class_elements(members);
670
671 for (const auto &m : members) {
672 if (!config().include_relations_also_as_members() &&
673 rendered_relations.find(m.name()) != rendered_relations.end())
674 continue;
675
676 generate_member(m, ostr);
677
678 ostr << '\n';
679 }
680
681 ostr << indent(1) << "}" << '\n';
682
683 if (config().generate_links) {
685 }
686
687 generate_notes(ostr, c);
688
689 for (const auto &member : c.members())
690 generate_member_notes(ostr, member, c.alias());
691
692 for (const auto &method : c.methods())
693 generate_member_notes(ostr, method, c.alias());
694}
695
697 const objc_interface &c, std::ostream &ostr) const
698{
699 namespace mermaid_common = clanguml::common::generators::mermaid;
700
701 //
702 // Process relationships
703 //
704 std::set<std::string> rendered_relations;
705
706 std::stringstream all_relations_str;
707 std::set<std::string> unique_relations;
708
709 for (const auto &r : c.relationships()) {
710 LOG_DBG("== Processing relationship {}", to_string(r.type()));
711
712 std::stringstream relstr;
713 eid_t destination{};
714 try {
715 destination = r.destination();
716
717 std::string relation_str;
718
719 if (!r.multiplicity_source().empty())
720 relation_str += "\"" + r.multiplicity_source() + "\" ";
721
722 relation_str += mermaid_common::to_mermaid(r.type());
723
724 if (!r.multiplicity_destination().empty())
725 relation_str += " \"" + r.multiplicity_destination() + "\"";
726
727 std::string target_alias;
728 try {
729 target_alias = model().to_alias(destination);
730 }
731 catch (...) {
732 LOG_DBG("Failed to find alias to {}", destination);
733 continue;
734 }
735
736 if (m_generated_aliases.find(target_alias) ==
738 continue;
739
740 if (r.type() == relationship_t::kExtension) {
741 relstr << indent(1) << target_alias << " <|-- " << c.alias();
742 }
743 else {
744 relstr << indent(1) << c.alias() << " " << relation_str << " "
745 << target_alias;
746 }
747
748 relstr << " : ";
749
750 if (!r.label().empty()) {
751 auto lbl = r.label();
752 if (r.type() == relationship_t::kFriendship)
753 lbl = "[friend]";
754 relstr << mermaid_common::to_mermaid(r.access()) << lbl;
755 rendered_relations.emplace(r.label());
756 }
757
758 if (unique_relations.count(relstr.str()) == 0) {
759 unique_relations.emplace(relstr.str());
760
761 LOG_TRACE("=== Adding relation {}", relstr.str());
762
763 all_relations_str << relstr.str() << '\n';
764 }
765 }
766 catch (error::uml_alias_missing &e) {
767 LOG_DBG("=== Skipping {} relation from {} to {} due "
768 "to: {}",
769 to_string(r.type()), c.full_name(true), destination, e.what());
770 }
771 }
772
773 ostr << all_relations_str.str();
774}
775
776void generator::generate_diagram(std::ostream &ostr) const
777{
779
780 generate_groups(ostr);
781
783}
784
786 const std::string &group_name, std::ostream &ostr) const
787{
788}
789
791 const std::string &group_name, std::ostream &ostr) const
792{
793}
794
795void generator::start_package(const package &p, std::ostream &ostr) const { }
796
797void generator::end_package(const package &p, std::ostream &ostr) const { }
798
799} // namespace clanguml::class_diagram::generators::mermaid