VisuTwin Canvas
C++ 3D Engine — Metal Backend
Loading...
Searching...
No Matches
path.h
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2025-2026 Arnis Lektauers
3#pragma once
4
5#include <initializer_list>
6#include <regex>
7#include <string>
8#include <utility>
9#include <vector>
10
11namespace visutwin::canvas
12{
13 class Path
14 {
15 public:
16 static constexpr char delimiter = '/';
17
18 static std::string join(std::initializer_list<std::string> sections)
19 {
20 return join(std::vector<std::string>(sections));
21 }
22
23 template<typename... Args>
24 static std::string join(const std::string& first, const Args&... rest)
25 {
26 std::vector<std::string> sections;
27 sections.reserve(1 + sizeof...(rest));
28 sections.push_back(first);
29 (sections.push_back(rest), ...);
30 return join(sections);
31 }
32
33 static std::string join(const std::vector<std::string>& sections)
34 {
35 if (sections.empty()) {
36 return {};
37 }
38
39 std::string result = sections.front();
40 for (size_t i = 0; i < sections.size() - 1; ++i) {
41 const std::string& one = sections[i];
42 const std::string& two = sections[i + 1];
43
44 if (!two.empty() && two[0] == delimiter) {
45 result = two;
46 continue;
47 }
48
49 if (!one.empty() && !two.empty() && one[one.length() - 1] != delimiter && two[0] != delimiter) {
50 result += delimiter;
51 result += two;
52 } else {
53 result += two;
54 }
55 }
56
57 return result;
58 }
59
60 static std::string normalize(const std::string& pathname)
61 {
62 const bool lead = !pathname.empty() && pathname.front() == delimiter;
63 const bool trail = !pathname.empty() && pathname.back() == delimiter;
64
65 std::vector<std::string> parts;
66 std::string token;
67 for (char ch : pathname) {
68 if (ch == delimiter) {
69 if (!token.empty()) {
70 parts.push_back(token);
71 token.clear();
72 }
73 } else {
74 token += ch;
75 }
76 }
77 if (!token.empty()) {
78 parts.push_back(token);
79 }
80
81 std::vector<std::string> cleaned;
82 for (const std::string& part : parts) {
83 if (part.empty() || part == ".") {
84 continue;
85 }
86 if (part == ".." && !cleaned.empty())
87 {
88 cleaned.pop_back();
89 continue;
90 }
91 cleaned.push_back(part);
92 }
93
94 std::string result;
95 if (lead) {
96 result += delimiter;
97 }
98
99 for (size_t i = 0; i < cleaned.size(); ++i) {
100 if (i > 0) {
101 result += delimiter;
102 }
103 result += cleaned[i];
104 }
105
106 if (trail && (result.empty() || result.back() != delimiter)) {
107 result += delimiter;
108 }
109
110 if (!lead && !result.empty() && result.front() == delimiter) {
111 result.erase(result.begin());
112 }
113
114 return result;
115 }
116
117 static std::pair<std::string, std::string> split(const std::string& pathname)
118 {
119 const size_t lastDelimiter = pathname.find_last_of(delimiter);
120 if (lastDelimiter == std::string::npos) {
121 return {"", pathname};
122 }
123
124 return {pathname.substr(0, lastDelimiter), pathname.substr(lastDelimiter + 1)};
125 }
126
127 static std::string getBasename(const std::string& pathname)
128 {
129 return split(pathname).second;
130 }
131
132 static std::string getDirectory(const std::string& pathname)
133 {
134 return split(pathname).first;
135 }
136
137 static std::string getExtension(const std::string& pathname)
138 {
139 const std::string pathNoQuery = pathname.substr(0, pathname.find('?'));
140 const size_t dot = pathNoQuery.find_last_of('.');
141 if (dot == std::string::npos) {
142 return {};
143 }
144 return pathNoQuery.substr(dot);
145 }
146
147 static bool isRelativePath(const std::string& pathname)
148 {
149 if (pathname.empty()) {
150 return true;
151 }
152
153 return pathname.front() != delimiter && !std::regex_search(pathname, std::regex(R"(:\/\/)"));
154 }
155
156 static std::string extractPath(const std::string& pathname)
157 {
158 std::vector<std::string> parts;
159 std::string token;
160 for (char ch : pathname) {
161 if (ch == delimiter) {
162 parts.push_back(token);
163 token.clear();
164 } else {
165 token += ch;
166 }
167 }
168 parts.push_back(token);
169
170 if (parts.size() <= 1) {
171 return {};
172 }
173
174 std::string result;
175 if (isRelativePath(pathname) && parts[0] != "." && parts[0] != "..") {
176 result = ".";
177 }
178
179 for (size_t i = 0; i + 1 < parts.size(); ++i) {
180 if (i > 0 || !result.empty()) {
181 result += '/';
182 }
183 result += parts[i];
184 }
185
186 return result;
187 }
188 };
189}
static std::string normalize(const std::string &pathname)
Definition path.h:60
static constexpr char delimiter
Definition path.h:16
static std::string extractPath(const std::string &pathname)
Definition path.h:156
static bool isRelativePath(const std::string &pathname)
Definition path.h:147
static std::string getExtension(const std::string &pathname)
Definition path.h:137
static std::pair< std::string, std::string > split(const std::string &pathname)
Definition path.h:117
static std::string getDirectory(const std::string &pathname)
Definition path.h:132
static std::string join(const std::string &first, const Args &... rest)
Definition path.h:24
static std::string join(std::initializer_list< std::string > sections)
Definition path.h:18
static std::string getBasename(const std::string &pathname)
Definition path.h:127
static std::string join(const std::vector< std::string > &sections)
Definition path.h:33