0.6.0
C++ to UML diagram generator based on Clang
Loading...
Searching...
No Matches
config.cc
Go to the documentation of this file.
1/**
2 * @file src/config/config.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
19#include "config.h"
20#include "diagram_templates.h"
21#include "glob/glob.hpp"
22
23#include <filesystem>
24
26
27std::string to_string(const hint_t t)
28{
29 switch (t) {
30 case hint_t::up:
31 return "up";
32 case hint_t::down:
33 return "down";
34 case hint_t::left:
35 return "left";
36 case hint_t::right:
37 return "right";
39 return "together";
40 case hint_t::row:
41 return "row";
42 case hint_t::column:
43 return "column";
44 default:
45 assert(false);
46 return "";
47 }
48}
49
50std::string to_string(element_filter_t::filtered_type ft)
51{
52 switch (ft) {
54 return "any";
56 return "class";
58 return "function";
60 return "method";
62 return "member";
64 return "enum";
66 return "concept";
68 return "package";
70 return "function_template";
72 return "objc_method";
74 return "objc_member";
76 return "objc_protocol";
78 return "objc_category";
80 return "objc_interface";
81 default:
82 assert(false);
83 return "";
84 }
85}
86
87std::string to_string(const method_arguments ma)
88{
89 switch (ma) {
91 return "full";
93 return "abbreviated";
95 return "none";
96 default:
97 assert(false);
98 return "";
99 }
100}
101
102std::string to_string(method_type mt)
103{
104 switch (mt) {
106 return "constructor";
108 return "destructor";
110 return "assignment";
112 return "operator";
114 return "defaulted";
116 return "deleted";
118 return "static";
119 }
120
121 assert(false);
122 return "";
123}
124
125std::string to_string(callee_type mt)
126{
127 switch (mt) {
129 return "constructor";
131 return "assignment";
133 return "operator";
135 return "defaulted";
137 return "static";
139 return "method";
141 return "function";
143 return "function_template";
145 return "lambda";
147 return "cuda_kernel";
149 return "cuda_device";
150 }
151
152 assert(false);
153 return "";
154}
155
156std::string to_string(const comment_parser_t cp)
157{
158 switch (cp) {
160 return "clang";
162 return "plain";
163 default:
164 assert(false);
165 return "";
166 }
167}
168
169std::string to_string(location_t cp)
170{
171 switch (cp) {
173 return "fileline";
175 return "function";
177 return "marker";
178 default:
179 assert(false);
180 return "";
181 }
182}
183
184std::string to_string(package_type_t pt)
185{
186 switch (pt) {
188 return "namespace";
190 return "directory";
192 return "module";
193 default:
194 assert(false);
195 return "";
196 }
197}
198
199std::string to_string(member_order_t mo)
200{
201 switch (mo) {
203 return "lexical";
205 return "as_is";
206 default:
207 assert(false);
208 return "";
209 }
210}
211std::string to_string(context_direction_t cd)
212{
213 switch (cd) {
215 return "inward";
217 return "outward";
219 return "any";
220 default:
221 assert(false);
222 return "";
223 }
224}
225
226std::string to_string(filter_mode_t fm)
227{
228 switch (fm) {
230 return "basic";
232 return "advanced";
233 default:
234 assert(false);
235 return "";
236 }
237}
238
239std::optional<std::string> plantuml::get_style(
240 const common::model::relationship_t relationship_type) const
241{
242 if (style.count(to_string(relationship_type)) == 0)
243 return {};
244
245 return style.at(to_string(relationship_type));
246}
247
248std::optional<std::string> plantuml::get_style(
249 const std::string &element_type) const
250{
251 if (style.count(element_type) == 0)
252 return {};
253
254 return style.at(element_type);
255}
256
258{
259 before.insert(before.end(), r.before.begin(), r.before.end());
260 after.insert(after.end(), r.after.begin(), r.after.end());
261 if (cmd.empty())
262 cmd = r.cmd;
263}
264
266{
267 before.insert(before.end(), r.before.begin(), r.before.end());
268 after.insert(after.end(), r.after.begin(), r.after.end());
269 if (cmd.empty())
270 cmd = r.cmd;
271}
272
274{
275 auto tmp = r.notes;
276 notes.merge(tmp);
277}
278
280{
281 return weakly_canonical(absolute(base_directory() / relative_to()));
282}
283
285 const inheritable_diagram_options &parent)
286{
287 glob.override(parent.glob);
288 using_namespace.override(parent.using_namespace);
294 include.override(parent.include);
295 exclude.override(parent.exclude);
296 puml.override(parent.puml);
297 mermaid.override(parent.mermaid);
298 graphml.override(parent.graphml);
310 generate_links.override(parent.generate_links);
312 git.override(parent.git);
327}
328
330 std::string full_name) const
331{
333 aliases.insert(type_aliases().begin(), type_aliases().end());
334
335 bool matched{true};
336 while (matched) {
337 auto matched_in_iteration{false};
338 for (const auto &[pattern, replacement] : aliases) {
339 matched_in_iteration =
340 util::replace_all(full_name, pattern, replacement) ||
341 matched_in_iteration;
342 }
343 matched = matched_in_iteration;
344 }
345
346 return full_name;
347}
348
350{
353}
354
355std::vector<std::string> diagram::glob_translation_units(
356 const std::vector<std::string> &compilation_database_files) const
357{
358
359 // Make sure that the paths are in preferred format for a given platform
360 // before intersecting the matches with compliation database
361 std::vector<std::string> compilation_database_paths;
362 compilation_database_paths.reserve(compilation_database_files.size());
363 for (const auto &cdf : compilation_database_files) {
364 std::filesystem::path p{cdf};
365 p.make_preferred();
366 compilation_database_paths.emplace_back(p.string());
367 }
368
369 // If glob is not defined use all translation units from the
370 // compilation database
371 if (!glob.has_value || (glob().include.empty() && glob().exclude.empty())) {
372 return compilation_database_paths;
373 }
374
375 // Otherwise, get all translation units matching the glob from diagram
376 // configuration
377 std::vector<std::string> glob_matches{};
378
379 LOG_DBG("Looking for translation units in {}", root_directory().string());
380
381 for (const auto &g : glob().include) {
382 if (g.is_regex()) {
383 LOG_DBG("Matching inclusive glob regex {}", g.to_string());
384
385 std::regex regex_pattern(
386 g.to_string(), std::regex_constants::optimize);
387
388 std::copy_if(compilation_database_paths.begin(),
389 compilation_database_paths.end(),
390 std::back_inserter(glob_matches),
391 [&regex_pattern](const auto &tu) {
392 std::smatch m;
393
394 return exists(std::filesystem::path{tu}) &&
395 std::regex_search(tu, m, regex_pattern);
396 });
397 }
398 else {
399 std::filesystem::path absolute_glob_path{g.to_string()};
400
401#ifdef _MSC_VER
402 if (!absolute_glob_path.has_root_name())
403#else
404 if (!absolute_glob_path.is_absolute())
405#endif
406 absolute_glob_path = root_directory() / absolute_glob_path;
407
408 LOG_DBG("Searching glob path {}", absolute_glob_path.string());
409
410 auto matches = glob::glob(absolute_glob_path.string(), true, false);
411
412 for (const auto &match : matches) {
413 const auto path =
414 std::filesystem::canonical(root_directory() / match);
415
416 glob_matches.emplace_back(path.string());
417 }
418 }
419 }
420
421 if (glob().include.empty())
422 glob_matches = compilation_database_paths;
423
424 for (const auto &g : glob().exclude) {
425 if (g.is_regex()) {
426 LOG_DBG("Matching exclusive glob regex {}", g.to_string());
427
428 std::regex regex_pattern(
429 g.to_string(), std::regex_constants::optimize);
430
431 for (const auto &cdf : compilation_database_paths) {
432 std::smatch m;
433 if (std::regex_search(cdf, m, regex_pattern)) {
434 glob_matches.erase(
435 remove(begin(glob_matches), end(glob_matches), cdf),
436 glob_matches.end());
437 }
438 }
439 }
440 else {
441 std::filesystem::path absolute_glob_path{g.to_string()};
442
443#ifdef _MSC_VER
444 if (!absolute_glob_path.has_root_name())
445#else
446 if (!absolute_glob_path.is_absolute())
447#endif
448 absolute_glob_path = root_directory() / absolute_glob_path;
449
450 LOG_DBG("Searching exclusive glob path {}",
451 absolute_glob_path.string());
452
453 auto matches = glob::glob(absolute_glob_path.string(), true, false);
454
455 for (const auto &match : matches) {
456 const auto path =
457 std::filesystem::canonical(root_directory() / match);
458
459 glob_matches.erase(remove(begin(glob_matches),
460 end(glob_matches), path.string()),
461 glob_matches.end());
462 }
463 }
464 }
465
466 // Calculate intersection between glob matches and compilation database
467 std::vector<std::string> result;
468 for (const auto &gm : glob_matches) {
469 std::filesystem::path gm_path{gm};
470 gm_path.make_preferred();
471 if (util::contains(compilation_database_paths, gm_path) ||
472 util::contains(compilation_database_files, gm)) {
473 result.emplace_back(gm_path.string());
474 }
475 }
476
477 return result;
478}
479
480std::filesystem::path diagram::make_path_relative(
481 const std::filesystem::path &p) const
482{
483 return relative(p, root_directory()).lexically_normal().string();
484}
485
486std::vector<std::string> diagram::make_module_relative(
487 const std::optional<std::string> &maybe_module) const
488{
489 if (!maybe_module)
490 return {};
491
492 auto module_path = common::model::path(
493 maybe_module.value(), common::model::path_type::kModule)
494 .tokens();
495
496 if (using_module.has_value) {
497 auto using_module_path = common::model::path(
498 using_module(), common::model::path_type::kModule)
499 .tokens();
500
501 if (util::starts_with(module_path, using_module_path)) {
502 util::remove_prefix(module_path, using_module_path);
503 }
504 }
505
506 return module_path;
507}
508
509std::optional<std::string> diagram::get_together_group(
510 const std::string &full_name) const
511{
512 const auto relative_name = using_namespace().relative(full_name);
513
514 for (const auto &[hint_target, hints] : layout()) {
515 for (const auto &hint : hints) {
516 if (hint.hint == hint_t::together) {
517 const auto &together_others =
518 std::get<std::vector<std::string>>(hint.entity);
519
520 if ((full_name == hint_target) ||
521 util::contains(together_others, full_name))
522 return hint_target;
523
524 if ((relative_name == hint_target) ||
525 util::contains(together_others, relative_name))
526 return hint_target;
527 }
528 }
529 }
530
531 return std::nullopt;
532}
533
534void diagram::initialize_type_aliases()
535{
536 if (type_aliases().count("std::basic_string<char>") == 0U) {
537 type_aliases().insert({"std::basic_string<char>", "std::string"});
538 }
539 if (type_aliases().count("std::basic_string<char,std::char_traits<"
540 "char>,std::allocator<char>>") == 0U) {
541 type_aliases().insert({"std::basic_string<char,std::char_traits<"
542 "char>,std::allocator<char>>",
543 "std::string"});
544 }
545 if (type_aliases().count("std::basic_string<wchar_t>") == 0U) {
546 type_aliases().insert({"std::basic_string<wchar_t>", "std::wstring"});
547 }
548 if (type_aliases().count("std::basic_string<char16_t>") == 0U) {
549 type_aliases().insert(
550 {"std::basic_string<char16_t>", "std::u16string"});
551 }
552 if (type_aliases().count("std::basic_string<char32_t>") == 0U) {
553 type_aliases().insert(
554 {"std::basic_string<char32_t>", "std::u32string"});
555 }
556 if (type_aliases().count("std::integral_constant<bool,true>") == 0U) {
557 type_aliases().insert(
558 {"std::integral_constant<bool,true>", "std::true_type"});
559 }
560 if (type_aliases().count("std::integral_constant<bool,false>") == 0U) {
561 type_aliases().insert(
562 {"std::integral_constant<bool,false>", "std::false_type"});
563 }
564#if LLVM_VERSION_MAJOR >= 16
565 if (type_aliases().count("std::basic_string") == 0U) {
566 type_aliases().insert({"std::basic_string", "std::string"});
567 }
568#endif
569}
570
571common::model::diagram_t class_diagram::type() const
572{
574}
575
576common::model::diagram_t sequence_diagram::type() const
577{
579}
580
581common::model::diagram_t package_diagram::type() const
582{
584}
585
586common::model::diagram_t include_diagram::type() const
587{
589}
590
591void class_diagram::initialize_relationship_hints()
592{
594
595 if (relationship_hints().count("std::vector") == 0U) {
596 relationship_hints().insert({"std::vector", {}});
597 }
598 if (relationship_hints().count("std::unique_ptr") == 0U) {
599 relationship_hints().insert({"std::unique_ptr", {}});
600 }
601 if (relationship_hints().count("std::shared_ptr") == 0U) {
602 relationship_hints().insert(
603 {"std::shared_ptr", {relationship_t::kAssociation}});
604 }
605 if (relationship_hints().count("std::weak_ptr") == 0U) {
606 relationship_hints().insert(
607 {"std::weak_ptr", {relationship_t::kAssociation}});
608 }
609 if (relationship_hints().count("std::tuple") == 0U) {
610 relationship_hints().insert({"std::tuple", {}});
611 }
612 if (relationship_hints().count("std::map") == 0U) {
613 relationship_hint_t hint{relationship_t::kNone};
614 hint.argument_hints.insert({1, relationship_t::kAggregation});
615 relationship_hints().insert({"std::tuple", std::move(hint)});
616 }
617}
618
619template <> void append_value<plantuml>(plantuml &l, const plantuml &r)
620{
621 l.append(r);
622}
623} // namespace clanguml::config