0.5.4
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-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#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.path_ == right.path_;
184 }
185
186 friend bool operator<(const path &left, const path &right)
187 {
188 return left.to_string() < right.to_string();
189 }
190
191 /**
192 * Render the path as string.
193 *
194 * @return String representation of the path.
195 */
196 std::string to_string() const
197 {
198 auto result =
199 fmt::format("{}", fmt::join(path_, std::string{separator()}));
200
202 util::replace_all(result, ".:", ":");
203 }
204
205 return result;
206 }
207
208 /**
209 * Whether the path is empty.
210 *
211 * @return
212 */
213 bool is_empty() const { return path_.empty(); }
214
215 /**
216 * Return the number of elements in the path.
217 *
218 * @return Size of path.
219 */
220 size_t size() const { return path_.size(); }
221
222 /**
223 * Append path to path.
224 *
225 * @param right Path to append at the end.
226 * @return New merged path.
227 */
228 path operator|(const path &right) const
229 {
230 path res{*this};
231 res.path_type_ = right.path_type_;
232 res.append(right);
233 return res;
234 }
235
236 /**
237 * Append path to the current path.
238 *
239 * @param right
240 */
241 void operator|=(const path &right) { append(right); }
242
243 /**
244 * Append path element to path.
245 *
246 * @return New path.
247 */
248 path operator|(const std::string &right) const
249 {
250 path res{*this};
251 res.append(right);
252 return res;
253 }
254
255 /**
256 * Append path element to the current path.
257 *
258 * @param right Path element to append.
259 */
260 void operator|=(const std::string &right) { append(right); }
261
262 std::string &operator[](const unsigned int index) { return path_[index]; }
263
264 const std::string &operator[](const unsigned int index) const
265 {
266 return path_[index];
267 }
268
269 /**
270 * Append path element to path.
271 *
272 * @return New path.
273 */
274 void append(const std::string &name) { path_.push_back(name); }
275
276 /**
277 * Append path to current path.
278 *
279 * @param ns Path to append.
280 */
281 void append(const path &ns)
282 {
283 for (const auto &n : ns) {
284 append(n);
285 }
286 }
287
288 /**
289 * Drop the last element of the path.
290 */
291 void pop_back()
292 {
293 if (!path_.empty()) {
294 path_.pop_back();
295 }
296 }
297
298 /**
299 * Get the parent of the last element in the path.
300 *
301 * @return Path to the parent of the last element, or nullopt.
302 */
303 std::optional<path> parent() const
304 {
305 if (size() <= 1) {
306 return {};
307 }
308
309 path res{*this};
310 res.pop_back();
311 return {std::move(res)};
312 }
313
314 /**
315 * Returns true if path starts with specified prefix.
316 * @param prefix Path prefix to check.
317 * @return
318 */
319 bool starts_with(const path &prefix) const
320 {
321 return util::starts_with(path_, prefix.path_);
322 }
323
324 /**
325 * Returns true if path ends with suffix
326 * @param suffix Path suffix to check
327 * @return
328 */
329 bool ends_with(const path &suffix) const
330 {
331 return util::ends_with(path_, suffix.path_);
332 }
333
334 /**
335 * @brief Returns the common prefix of 2 paths.
336 *
337 * If no common prefix exists between 2 paths, the result is an empty path.
338 *
339 * @param right Path to compare
340 * @return Common path prefix
341 */
342 path common_path(const path &right) const
343 {
344 path res{};
345 for (auto i = 0U; i < std::min(size(), right.size()); i++) {
346 if (path_[i] == right[i])
347 res |= path_[i];
348 else
349 break;
350 }
351 return res;
352 }
353
354 /**
355 * Make the current path relative to the other path, if possible.
356 *
357 * If not, return the original path.
358 *
359 * @param right Parent path
360 * @return Path relative to `right`
361 */
362 path relative_to(const path &right) const
363 {
364 path res{*this};
365
366 if (res.starts_with(right))
367 util::remove_prefix(res.path_, right.path_);
368
369 return res;
370 }
371
372 /**
373 * Make path represented as a string relative to the current path.
374 *
375 * @param ns Path to make relative against *this.
376 * @return Relative path.
377 */
378 std::string relative(const std::string &ns) const
379 {
380 if (is_empty())
381 return ns;
382
383 if (ns == to_string())
384 return ns;
385
386 auto res = ns;
387 auto ns_prefix = to_string() + std::string{separator()};
388
389 auto it = res.find(ns_prefix);
390 while (it != std::string::npos) {
391 res.erase(it, ns_prefix.size());
392 it = res.find(ns_prefix);
393 }
394
395 return res;
396 }
397
398 /**
399 * Return the name of the last element in the path.
400 *
401 * @return Name of the last element in the path.
402 */
403 std::string name() const
404 {
405 assert(size() > 0);
406
407 return path_.back();
408 }
409
410 path::container_type::iterator begin() { return path_.begin(); }
411 path::container_type::iterator end() { return path_.end(); }
412
413 path::container_type::const_iterator cbegin() const
414 {
415 return path_.cbegin();
416 }
417 path::container_type::const_iterator cend() const { return path_.cend(); }
418
419 path::container_type::const_iterator begin() const { return path_.begin(); }
420 path::container_type::const_iterator end() const { return path_.end(); }
421
422 /**
423 * Get path type.
424 *
425 * @return Path type.
426 */
427 path_type type() const { return path_type_; }
428
429 const container_type &tokens() const { return path_; }
430
431private:
434};
435
436} // namespace clanguml::common::model