0.6.2
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);
314 generate_links.override(parent.generate_links);
316 git.override(parent.git);
332}
333
335 std::string full_name) const
336{
338 aliases.insert(type_aliases().begin(), type_aliases().end());
339
340 bool matched{true};
341 while (matched) {
342 auto matched_in_iteration{false};
343 for (const auto &[pattern, replacement] : aliases) {
344 matched_in_iteration =
345 util::replace_all(full_name, pattern, replacement) ||
346 matched_in_iteration;
347 }
348 matched = matched_in_iteration;
349 }
350
351 return full_name;
352}
353
355{
358}
359
360std::vector<std::string> diagram::glob_translation_units(
361 const std::vector<std::string> &compilation_database_files,
362 bool is_fixed) const
363{
364 // Make sure that the paths are in preferred format for a given platform
365 // before intersecting the matches with compliation database
366 std::vector<std::string> compilation_database_paths;
367 compilation_database_paths.reserve(compilation_database_files.size());
368 for (const auto &cdf : compilation_database_files) {
369 std::filesystem::path p{cdf};
370 p.make_preferred();
371 compilation_database_paths.emplace_back(p.string());
372 }
373
374 // If glob is not defined use all translation units from the
375 // compilation database
376 if (!glob.has_value || (glob().include.empty() && glob().exclude.empty())) {
377 return compilation_database_paths;
378 }
379
380 // Create also a canonical database paths set
381 std::vector<std::string> canonical_compilation_database_paths;
382 canonical_compilation_database_paths.reserve(
383 compilation_database_paths.size());
384 for (const auto &cdp : compilation_database_paths) {
385 auto canonical_p = weakly_canonical(std::filesystem::path{cdp});
386 canonical_compilation_database_paths.emplace_back(canonical_p.string());
387 }
388
389 // Otherwise, get all translation units matching the glob from diagram
390 // configuration
391 std::vector<std::string> glob_matches{};
392
393 LOG_DBG("Looking for translation units in {}", root_directory().string());
394
395 for (const auto &g : glob().include) {
396 if (g.is_regex()) {
397 LOG_DBG("Matching inclusive glob regex {}", g.to_string());
398
399 std::regex regex_pattern(
400 g.to_string(), std::regex_constants::optimize);
401
402 std::copy_if(compilation_database_paths.begin(),
403 compilation_database_paths.end(),
404 std::back_inserter(glob_matches),
405 [&regex_pattern](const auto &tu) {
406 std::smatch m;
407
408 return exists(std::filesystem::path{tu}) &&
409 std::regex_search(tu, m, regex_pattern);
410 });
411 }
412 else {
413 std::filesystem::path absolute_glob_path{g.to_string()};
414
415#ifdef _MSC_VER
416 if (!absolute_glob_path.has_root_name())
417#else
418 if (!absolute_glob_path.is_absolute())
419#endif
420 absolute_glob_path = root_directory() / absolute_glob_path;
421
422 LOG_DBG("Searching glob path {}", absolute_glob_path.string());
423
424 auto matches = glob::glob(absolute_glob_path.string(), true, false);
425
426 for (const auto &match : matches) {
427 const auto path =
428 std::filesystem::canonical(root_directory() / match);
429
430 glob_matches.emplace_back(path.string());
431 }
432 }
433 }
434
435 if (glob().include.empty())
436 glob_matches = compilation_database_paths;
437
438 for (const auto &g : glob().exclude) {
439 if (g.is_regex()) {
440 LOG_DBG("Matching exclusive glob regex {}", g.to_string());
441
442 std::regex regex_pattern(
443 g.to_string(), std::regex_constants::optimize);
444
445 for (const auto &cdf : compilation_database_paths) {
446 std::smatch m;
447 if (std::regex_search(cdf, m, regex_pattern)) {
448 glob_matches.erase(
449 remove(begin(glob_matches), end(glob_matches), cdf),
450 glob_matches.end());
451 }
452 }
453 }
454 else {
455 std::filesystem::path absolute_glob_path{g.to_string()};
456
457#ifdef _MSC_VER
458 if (!absolute_glob_path.has_root_name())
459#else
460 if (!absolute_glob_path.is_absolute())
461#endif
462 absolute_glob_path = root_directory() / absolute_glob_path;
463
464 LOG_DBG("Searching exclusive glob path {}",
465 absolute_glob_path.string());
466
467 auto matches = glob::glob(absolute_glob_path.string(), true, false);
468
469 for (const auto &match : matches) {
470 const auto path =
471 std::filesystem::canonical(root_directory() / match);
472
473 glob_matches.erase(remove(begin(glob_matches),
474 end(glob_matches), path.string()),
475 glob_matches.end());
476 }
477 }
478 }
479
480 // Calculate intersection between glob matches and compilation database
481 std::vector<std::string> result;
482 for (const auto &gm : glob_matches) {
483 std::filesystem::path gm_path{gm};
484 gm_path.make_preferred();
485 if (is_fixed || util::contains(compilation_database_paths, gm_path) ||
486 util::contains(canonical_compilation_database_paths, gm_path) ||
487 util::contains(compilation_database_files, gm)) {
488 result.emplace_back(gm_path.string());
489 }
490 }
491
492 return result;
493}
494
495std::filesystem::path diagram::make_path_relative(
496 const std::filesystem::path &p) const
497{
498 return relative(p, root_directory()).lexically_normal().string();
499}
500
501std::vector<std::string> diagram::make_module_relative(
502 const std::optional<std::string> &maybe_module) const
503{
504 if (!maybe_module)
505 return {};
506
507 auto module_path = common::model::path(
508 maybe_module.value(), common::model::path_type::kModule)
509 .tokens();
510
511 if (using_module.has_value) {
512 auto using_module_path = common::model::path(
513 using_module(), common::model::path_type::kModule)
514 .tokens();
515
516 if (util::starts_with(module_path, using_module_path)) {
517 util::remove_prefix(module_path, using_module_path);
518 }
519 }
520
521 return module_path;
522}
523
524std::optional<std::string> diagram::get_together_group(
525 const std::string &full_name) const
526{
527 const auto relative_name = using_namespace().relative(full_name);
528
529 for (const auto &[hint_target, hints] : layout()) {
530 for (const auto &hint : hints) {
531 if (hint.hint == hint_t::together) {
532 const auto &together_others =
533 std::get<std::vector<std::string>>(hint.entity);
534
535 if ((full_name == hint_target) ||
536 util::contains(together_others, full_name))
537 return hint_target;
538
539 if ((relative_name == hint_target) ||
540 util::contains(together_others, relative_name))
541 return hint_target;
542 }
543 }
544 }
545
546 return std::nullopt;
547}
548
549void diagram::initialize_type_aliases()
550{
551 if (type_aliases().count("std::basic_string<char>") == 0U) {
552 type_aliases().insert({"std::basic_string<char>", "std::string"});
553 }
554 if (type_aliases().count("std::basic_string<char,std::char_traits<"
555 "char>,std::allocator<char>>") == 0U) {
556 type_aliases().insert({"std::basic_string<char,std::char_traits<"
557 "char>,std::allocator<char>>",
558 "std::string"});
559 }
560 if (type_aliases().count("std::basic_string<wchar_t>") == 0U) {
561 type_aliases().insert({"std::basic_string<wchar_t>", "std::wstring"});
562 }
563 if (type_aliases().count("std::basic_string<char16_t>") == 0U) {
564 type_aliases().insert(
565 {"std::basic_string<char16_t>", "std::u16string"});
566 }
567 if (type_aliases().count("std::basic_string<char32_t>") == 0U) {
568 type_aliases().insert(
569 {"std::basic_string<char32_t>", "std::u32string"});
570 }
571 if (type_aliases().count("std::integral_constant<bool,true>") == 0U) {
572 type_aliases().insert(
573 {"std::integral_constant<bool,true>", "std::true_type"});
574 }
575 if (type_aliases().count("std::integral_constant<bool,false>") == 0U) {
576 type_aliases().insert(
577 {"std::integral_constant<bool,false>", "std::false_type"});
578 }
579#if LLVM_VERSION_MAJOR >= 16
580 if (type_aliases().count("std::basic_string") == 0U) {
581 type_aliases().insert({"std::basic_string", "std::string"});
582 }
583#endif
584}
585
586common::model::diagram_t class_diagram::type() const
587{
589}
590
591common::model::diagram_t sequence_diagram::type() const
592{
594}
595
596common::model::diagram_t package_diagram::type() const
597{
599}
600
601common::model::diagram_t include_diagram::type() const
602{
604}
605
606void class_diagram::initialize_relationship_hints()
607{
609
610 if (relationship_hints().count("std::vector") == 0U) {
611 relationship_hints().insert({"std::vector", {}});
612 }
613 if (relationship_hints().count("std::unique_ptr") == 0U) {
614 relationship_hints().insert({"std::unique_ptr", {}});
615 }
616 if (relationship_hints().count("std::shared_ptr") == 0U) {
617 relationship_hints().insert(
618 {"std::shared_ptr", {relationship_t::kAssociation}});
619 }
620 if (relationship_hints().count("std::weak_ptr") == 0U) {
621 relationship_hints().insert(
622 {"std::weak_ptr", {relationship_t::kAssociation}});
623 }
624 if (relationship_hints().count("std::tuple") == 0U) {
625 relationship_hints().insert({"std::tuple", {}});
626 }
627 if (relationship_hints().count("std::map") == 0U) {
628 relationship_hint_t hint{relationship_t::kNone};
629 hint.argument_hints.insert({1, relationship_t::kAggregation});
630 relationship_hints().insert({"std::tuple", std::move(hint)});
631 }
632}
633
634template <> void append_value<plantuml>(plantuml &l, const plantuml &r)
635{
636 l.append(r);
637}
638} // namespace clanguml::config