0.6.0
C++ to UML diagram generator based on Clang
Loading...
Searching...
No Matches
path.h
Go to the documentation of this file.
1/**
2 * @file src/common/model/path.h
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#pragma once
19
20#include "util/util.h"
21
22#include <optional>
23#include <string>
24#include <vector>
25
27
28/**
29 * @brief Type of diagram path
30 *
31 * Paths in diagrams represent the nest structure within a diagram, e.g.
32 * a nested set of namespaces or nested set of directories.
33 */
34enum class path_type {
35 kNamespace, /*!< Namespace path */
36 kFilesystem, /*!< Filesystem path */
37 kModule /*!< Module path */
38};
39
40std::string to_string(path_type pt);
41
42/**
43 * @brief Diagram path
44 *
45 * This class stores a diagram path, such as a namespace or directory
46 * structure.
47 */
48class path {
49
50 /**
51 * Returns the path separator based on the path type.
52 *
53 * @return Path separator
54 */
55 static const char *separator(path_type pt)
56 {
57 switch (pt) {
59 return "::";
61 return ".";
63#ifdef _WIN32
64 return "\\";
65#else
66 return "/";
67#endif
68 }
69
70 return "::";
71 }
72
73 /**
74 * Returns the path separator based on the type of the instance path.
75 *
76 * @return Path separator
77 */
78 const char *separator() const { return separator(path_type_); }
79
80public:
81 using container_type = std::vector<std::string>;
82
84 const std::string &ns, path_type pt = path_type::kNamespace)
85 {
86 container_type result;
87 if (pt == path_type::kModule) {
88 auto path_toks = util::split(ns, separator(pt));
89 for (const auto &pt : path_toks) {
90 const auto subtoks = util::split(pt, ":");
91 if (subtoks.size() == 2) {
92 result.push_back(subtoks.at(0));
93 result.push_back(fmt::format(":{}", subtoks.at(1)));
94 }
95 else
96 result.push_back(subtoks.at(0));
97 }
98 }
99 else
100 result = util::split(ns, separator(pt));
101
102 return result;
103 }
104
106 : path_type_{pt}
107 {
108 }
109
110 path(const std::string &ns, path_type pt = path_type::kNamespace)
111 : path_type_{pt}
112 {
113 if (ns.empty())
114 return;
115
116 path_ = split(ns, pt);
117 }
118
119 virtual ~path() = default;
120
121 path(container_type::const_iterator begin,
122 container_type::const_iterator end,
124 : path(pt)
125 {
126 if (begin == end)
127 return;
128
129 std::copy(begin, end, std::back_inserter(path_));
130 }
131
132 path(const path &right) = default;
133
134 path &operator=(const path &right)
135 {
136 if (&right == this)
137 return *this;
138
139 if (path_type_ != right.path_type_)
140 throw std::runtime_error(
141 "Cannot assign a path to a path with another path type.");
142
143 path_type_ = right.path_type_;
144 path_ = right.path_;
145
146 return *this;
147 }
148
149 path(path &&right) noexcept = default;
150
151 path &operator=(path &&right) noexcept = default;
152
153 path(std::initializer_list<std::string> ns,
155 : path(pt)
156 {
157 if ((ns.size() == 1) &&
158 util::contains(*ns.begin(), std::string{separator()})) {
159 path_ = util::split(*ns.begin(), separator());
160 }
161 else if ((ns.size() == 1) && ns.begin()->empty()) {
162 }
163 else
164 path_ = ns;
165 }
166
167 explicit path(const std::vector<std::string> &ns,
169 : path(pt)
170 {
171 if ((ns.size() == 1) &&
172 util::contains(*ns.begin(), std::string{separator()})) {
173 path_ = util::split(*ns.begin(), separator());
174 }
175 else if ((ns.size() == 1) && ns.begin()->empty()) {
176 }
177 else
178 path_ = ns;
179 }
180
181 friend bool operator==(const path &left, const path &right)
182 {
183 return (left.is_root() == right.is_root()) &&
184 (left.path_ == right.path_);
185 }
186
187 friend bool operator<(const path &left, const path &right)
188 {
189 return left.to_string() < right.to_string();
190 }
191
192 /**
193 * Render the path as string.
194 *
195 * @return String representation of the path.
196 */
197 std::string to_string() const
198 {
199 auto result =
200 fmt::format("{}", fmt::join(path_, std::string{separator()}));
201
203 util::replace_all(result, ".:", ":");
204 }
205
206 return result;
207 }
208
209 /**
210 * Whether the path is empty.
211 *
212 * @return
213 */
214 bool is_empty() const { return path_.empty(); }
215
216 bool is_root() const { return is_root_; }
217
218 void is_root(bool r)
219 {
221 return;
222 is_root_ = r;
223 }
224
225 /**
226 * Return the number of elements in the path.
227 *
228 * @return Size of path.
229 */
230 size_t size() const { return path_.size(); }
231
232 /**
233 * Append path to path.
234 *
235 * @param right Path to append at the end.
236 * @return New merged path.
237 */
238 path operator|(const path &right) const
239 {
240 path res{*this};
241 res.path_type_ = right.path_type_;
242 res.append(right);
243 return res;
244 }
245
246 /**
247 * Append path to the current path.
248 *
249 * @param right
250 */
251 void operator|=(const path &right) { append(right); }
252
253 /**
254 * Append path element to path.
255 *
256 * @return New path.
257 */
258 path operator|(const std::string &right) const
259 {
260 path res{*this};
261 res.append(right);
262 return res;
263 }
264
265 /**
266 * Append path element to the current path.
267 *
268 * @param right Path element to append.
269 */
270 void operator|=(const std::string &right) { append(right); }
271
272 std::string &operator[](const unsigned int index) { return path_[index]; }
273
274 const std::string &operator[](const unsigned int index) const
275 {
276 return path_[index];
277 }
278
279 /**
280 * Append path element to path.
281 *
282 * @return New path.
283 */
284 void append(const std::string &name) { path_.push_back(name); }
285
286 /**
287 * Append path to current path.
288 *
289 * @param ns Path to append.
290 */
291 void append(const path &ns)
292 {
293 for (const auto &n : ns) {
294 append(n);
295 }
296 }
297
298 /**
299 * Drop the last element of the path.
300 */
301 void pop_back()
302 {
303 if (!path_.empty()) {
304 path_.pop_back();
305 }
306 }
307
308 /**
309 * Get the parent of the last element in the path.
310 *
311 * @return Path to the parent of the last element, or nullopt.
312 */
313 std::optional<path> parent() const
314 {
315 if (size() <= 1) {
316 return {};
317 }
318
319 path res{*this};
320 res.pop_back();
321 return {std::move(res)};
322 }
323
324 /**
325 * Returns true if path starts with specified prefix.
326 * @param prefix Path prefix to check.
327 * @return
328 */
329 bool starts_with(const path &prefix) const
330 {
331 return util::starts_with(path_, prefix.path_);
332 }
333
334 /**
335 * Returns true if path ends with suffix
336 * @param suffix Path suffix to check
337 * @return
338 */
339 bool ends_with(const path &suffix) const
340 {
341 return util::ends_with(path_, suffix.path_);
342 }
343
344 /**
345 * @brief Returns the common prefix of 2 paths.
346 *
347 * If no common prefix exists between 2 paths, the result is an empty path.
348 *
349 * @param right Path to compare
350 * @return Common path prefix
351 */
352 path common_path(const path &right) const
353 {
354 path res{};
355 for (auto i = 0U; i < std::min(size(), right.size()); i++) {
356 if (path_[i] == right[i])
357 res |= path_[i];
358 else
359 break;
360 }
361 return res;
362 }
363
364 /**
365 * Make the current path relative to the other path, if possible.
366 *
367 * If not, return the original path.
368 *
369 * @param right Parent path
370 * @return Path relative to `right`
371 */
372 path relative_to(const path &right) const
373 {
374 path res{*this};
375 res.is_root(true);
376
377 if (res.starts_with(right)) {
378 util::remove_prefix(res.path_, right.path_);
379 res.is_root(false);
380 }
381
382 return res;
383 }
384
385 /**
386 * Make path represented as a string relative to the current path.
387 *
388 * @param ns Path to make relative against *this.
389 * @return Relative path.
390 */
391 std::string relative(const std::string &ns) const
392 {
393 if (is_empty())
394 return ns;
395
396 if (ns == to_string())
397 return ns;
398
399 auto res = ns;
400 auto ns_prefix = to_string() + std::string{separator()};
401
402 auto it = res.find(ns_prefix);
403
404 while (it != std::string::npos) {
405 res.erase(it, ns_prefix.size());
406 it = res.find(ns_prefix);
407 }
408
409 return res;
410 }
411
412 /**
413 * Return the name of the last element in the path.
414 *
415 * @return Name of the last element in the path.
416 */
417 std::string name() const
418 {
419 assert(size() > 0);
420
421 return path_.back();
422 }
423
424 path::container_type::iterator begin() { return path_.begin(); }
425 path::container_type::iterator end() { return path_.end(); }
426
427 path::container_type::const_iterator cbegin() const
428 {
429 return path_.cbegin();
430 }
431 path::container_type::const_iterator cend() const { return path_.cend(); }
432
433 path::container_type::const_iterator begin() const { return path_.begin(); }
434 path::container_type::const_iterator end() const { return path_.end(); }
435
436 /**
437 * Get path type.
438 *
439 * @return Path type.
440 */
441 path_type type() const { return path_type_; }
442
443 const container_type &tokens() const { return path_; }
444
445private:
448 bool is_root_{false};
449};
450
451} // namespace clanguml::common::model