LogIt++
Loading...
Searching...
No Matches
path_utils.hpp
Go to the documentation of this file.
1#ifndef _LOGIT_PATH_UTILS_HPP_INCLUDED
2#define _LOGIT_PATH_UTILS_HPP_INCLUDED
3
6
7#include <string>
8#if __cplusplus >= 201703L
9#include <filesystem>
10#else
11#include <vector>
12#include <cctype>
13#include <stdexcept>
14#endif
15
16#ifdef _WIN32
17// For Windows systems
18#include <direct.h>
19#include <windows.h>
20#include <locale>
21#include <codecvt>
22#else
23// For POSIX systems
24#include <unistd.h>
25#include <limits.h>
26#include <dirent.h>
27#include <sys/stat.h>
28#include <errno.h>
29#endif
30
31namespace logit {
32# if __cplusplus >= 201703L
33 namespace fs = std::filesystem;
34# endif
35
36#if defined(__EMSCRIPTEN__)
37
38 inline std::string get_exec_dir() { return "./"; }
39
40 inline std::vector<std::string> get_list_files(const std::string&) {
41 std::cerr << "get_list_files is not supported under Emscripten" << std::endl;
42 return {};
43 }
44
45 inline std::string get_file_name(const std::string& file_path) {
46 size_t pos = file_path.find_last_of("/\\");
47 if (pos == std::string::npos) return file_path;
48 return file_path.substr(pos + 1);
49 }
50
51 inline std::string make_relative(const std::string& file_path, const std::string&) {
52 return file_path;
53 }
54
55 inline void create_directories(const std::string&) {
56 std::cerr << "create_directories is not supported under Emscripten" << std::endl;
57 }
58
59 inline bool is_file(const std::string& path) {
60 size_t dot_pos = path.find_last_of('.');
61 size_t slash_pos = path.find_last_of("/\\");
62 return (dot_pos != std::string::npos && (slash_pos == std::string::npos || dot_pos > slash_pos));
63 }
64
65#else
66
69 std::string get_exec_dir() {
70# ifdef _WIN32
71 std::vector<wchar_t> buffer(MAX_PATH);
72 HMODULE hModule = GetModuleHandle(NULL);
73
74 // Try to get the path
75 std::size_t size = static_cast<std::size_t>(GetModuleFileNameW(hModule, buffer.data(), static_cast<DWORD>(buffer.size())));
76
77 // If the path is too long, increase the buffer size
78 while (size == buffer.size() && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
79 buffer.resize(buffer.size() * 2); // Double the buffer size
80 size = static_cast<std::size_t>(GetModuleFileNameW(hModule, buffer.data(), static_cast<DWORD>(buffer.size())));
81 }
82
83 if (size == 0) {
84 throw std::runtime_error("Failed to get executable path.");
85 }
86
87 std::wstring exe_path(buffer.begin(), buffer.begin() + size);
88
89 // Trim the path to the directory (remove the file name, keep only the folder path)
90 size_t pos = exe_path.find_last_of(L"\\/");
91 if (pos != std::wstring::npos) {
92 exe_path = exe_path.substr(0, pos);
93 }
94
95 // Convert from std::wstring (UTF-16) to std::string (UTF-8)
96 std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
97 return converter.to_bytes(exe_path);
98# else
99 char result[PATH_MAX];
100 ssize_t count = readlink("/proc/self/exe", result, PATH_MAX);
101
102 if (count == -1) {
103 throw std::runtime_error("Failed to get executable path.");
104 }
105
106 std::string exe_path(result, count);
107
108 // Trim the path to the directory (remove the file name, keep only the folder path)
109 size_t pos = exe_path.find_last_of("\\/");
110 if (pos != std::string::npos) {
111 exe_path = exe_path.substr(0, pos);
112 }
113
114 return exe_path;
115# endif
116 }
117
121 std::vector<std::string> get_list_files(const std::string& path) {
122 std::vector<std::string> list_files;
123# ifdef _WIN32
124 // Use wide versions of functions to correctly handle non-ASCII characters.
125 std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
126 std::wstring wsearch_path;
127
128 // If the path is empty, use the current directory.
129 if (path.empty()) {
130 wchar_t buffer[MAX_PATH];
131 GetCurrentDirectoryW(MAX_PATH, buffer);
132 wsearch_path = buffer;
133 } else {
134 wsearch_path = converter.from_bytes(path);
135 }
136
137 // Ensure there is a trailing separator.
138 if (!wsearch_path.empty()) {
139 wchar_t last_char = wsearch_path.back();
140 if (last_char != L'\\' && last_char != L'/') {
141 wsearch_path.push_back(L'\\');
142 }
143 }
144
145 // Create the search pattern.
146 std::wstring pattern = wsearch_path + L"*";
147 WIN32_FIND_DATAW fd;
148 HANDLE hFind = FindFirstFileW(pattern.c_str(), &fd);
149 if (hFind != INVALID_HANDLE_VALUE) {
150 do {
151 if (wcscmp(fd.cFileName, L".") == 0 || wcscmp(fd.cFileName, L"..") == 0)
152 continue;
153
154 std::wstring wfull_path = wsearch_path + fd.cFileName;
155
156 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
157 // Recursively process subdirectories.
158 std::vector<std::string> sub_files = get_list_files(converter.to_bytes(wfull_path));
159 list_files.insert(list_files.end(), sub_files.begin(), sub_files.end());
160 } else {
161 // Add the found file.
162 list_files.push_back(converter.to_bytes(wfull_path));
163 }
164 } while (FindNextFileW(hFind, &fd));
165 FindClose(hFind);
166 }
167# else
168 // Implementation for POSIX systems.
169 std::string search_path = path;
170 if (search_path.empty()) {
171 char buffer[PATH_MAX];
172 if (getcwd(buffer, PATH_MAX)) {
173 search_path = buffer;
174 }
175 }
176 // Ensure there is a trailing separator.
177 if (search_path.back() != '/' && search_path.back() != '\\') {
178 search_path.push_back('/');
179 }
180 DIR* dir = opendir(search_path.c_str());
181 if (dir) {
182 struct dirent* entry;
183 while ((entry = readdir(dir)) != nullptr) {
184 std::string file_name = entry->d_name;
185 if (file_name == "." || file_name == "..")
186 continue;
187 std::string full_path = search_path + file_name;
188 struct stat statbuf;
189 if (stat(full_path.c_str(), &statbuf) == 0) {
190 if (S_ISDIR(statbuf.st_mode)) {
191 std::vector<std::string> sub_files = get_list_files(full_path);
192 list_files.insert(list_files.end(), sub_files.begin(), sub_files.end());
193 } else if (S_ISREG(statbuf.st_mode)) {
194 list_files.push_back(full_path);
195 }
196 }
197 }
198 closedir(dir);
199 }
200# endif
201 return list_files;
202 }
203
207 std::string get_file_name(const std::string& file_path) {
208# if __cplusplus >= 201703L
209 return fs::u8path(file_path).filename().u8string();
210# else
211 size_t pos = file_path.find_last_of("/\\");
212 if (pos == std::string::npos) return file_path;
213 return file_path.substr(pos + 1);
214# endif
215 }
216
217#if __cplusplus >= 201703L
218
223 inline std::string make_relative(const std::string& file_path, const std::string& base_path) {
224 if (base_path.empty()) return file_path;
225 std::filesystem::path fileP = std::filesystem::u8path(file_path);
226 std::filesystem::path baseP = std::filesystem::u8path(base_path);
227 std::error_code ec; // For exception-safe operation
228 std::filesystem::path relativeP = std::filesystem::relative(fileP, baseP, ec);
229 if (ec) {
230 // If there is an error, return the original file_path
231 return file_path;
232 } else {
233 return relativeP.u8string();
234 }
235 }
236
240 void create_directories(const std::string& path) {
241# ifdef _WIN32
242 // Convert UTF-8 string to wide string for Windows
243 std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
244 std::wstring wide_path = converter.from_bytes(path);
245 std::filesystem::path dir(wide_path);
246# else
247 std::filesystem::path dir = std::filesystem::u8path(path);
248# endif
249 if (!std::filesystem::exists(dir)) {
250 std::error_code ec;
251 if (!std::filesystem::create_directories(dir, ec)) {
252 throw std::runtime_error("Failed to create directories for path: " + dir.u8string());
253 }
254 }
255 }
256
257#else
258
262 std::string root;
263 std::vector<std::string> components;
264 };
265
269 PathComponents split_path(const std::string& path) {
270 PathComponents result;
271 size_t i = 0;
272 size_t n = path.size();
273
274 // Handle root paths for Unix and Windows
275 if (n >= 1 && (path[0] == '/' || path[0] == '\\')) {
276 // Unix root "/"
277 result.root = "/";
278 ++i;
279 } else if (n >= 2 && std::isalpha(path[0]) && path[1] == ':') {
280 // Windows drive letter "C:"
281 result.root = path.substr(0, 2);
282 i = 2;
283 if (n >= 3 && (path[2] == '/' || path[2] == '\\')) {
284 // "C:/"
285 ++i;
286 }
287 }
288
289 // Split the path into components
290 while (i < n) {
291 // Skip path separators
292 while (i < n && (path[i] == '/' || path[i] == '\\')) {
293 ++i;
294 }
295 // Find the next separator
296 size_t j = i;
297 while (j < n && path[j] != '/' && path[j] != '\\') {
298 ++j;
299 }
300 if (i < j) {
301 result.components.push_back(path.substr(i, j - i));
302 i = j;
303 }
304 }
305
306 return result;
307 }
308
313 std::string make_relative(const std::string& file_path, const std::string& base_path) {
314 if (base_path.empty()) return file_path;
315 PathComponents file_pc = split_path(file_path);
316 PathComponents base_pc = split_path(base_path);
317
318 // If roots are different, return the original file_path
319 if (file_pc.root != base_pc.root) {
320 return file_path;
321 }
322
323 // Find the common prefix components
324 size_t common_size = 0;
325 while (common_size < file_pc.components.size() &&
326 common_size < base_pc.components.size() &&
327 file_pc.components[common_size] == base_pc.components[common_size]) {
328 ++common_size;
329 }
330
331 // Build the relative path components
332 std::vector<std::string> relative_components;
333
334 // Add ".." for each remaining component in base path
335 for (size_t i = common_size; i < base_pc.components.size(); ++i) {
336 relative_components.push_back("..");
337 }
338
339 // Add the remaining components from the file path
340 for (size_t i = common_size; i < file_pc.components.size(); ++i) {
341 relative_components.push_back(file_pc.components[i]);
342 }
343
344 // Join the components into a relative path string
345 std::string relative_path;
346 if (relative_components.empty()) {
347 relative_path = ".";
348 } else {
349 for (size_t i = 0; i < relative_components.size(); ++i) {
350 if (i > 0) {
351# ifdef _WIN32
352 relative_path += '\\'; // Windows
353# else
354 relative_path += '/';
355# endif
356 }
357 relative_path += relative_components[i];
358 }
359 }
360
361 return relative_path;
362 }
363
367 inline bool is_file(const std::string& path) {
368 size_t dot_pos = path.find_last_of('.');
369 size_t slash_pos = path.find_last_of("/\\");
370 return (dot_pos != std::string::npos && (slash_pos == std::string::npos || dot_pos > slash_pos));
371 }
372
376 void create_directories(const std::string& path) {
377 if (path.empty()) return;
378 PathComponents path_pc = split_path(path);
379 auto &components = path_pc.components;
380 size_t components_size = components.size();
381
382 // Check if the last component is a file
383 if (is_file(path)) {
384 --components_size;
385 }
386
387 // Build the path incrementally and create directories
388 std::string current_path = path_pc.root;
389 for (size_t i = 0; i < components_size; ++i) {
390 if (!current_path.empty() && current_path.back() != '/' && current_path.back() != '\\') {
391 current_path += '/';
392 }
393 current_path += components[i];
394
395 // Skip special components
396 if (components[i] == ".." ||
397 components[i] == "/" ||
398 components[i] == "~/") continue;
399# ifdef _WIN32
400 int ret = _mkdir(utf8_to_ansi(current_path).c_str());
401# else
402 int ret = mkdir(current_path.c_str(), 0755);
403# endif
404 int errnum = errno;
405 if (ret != 0 && errnum != EEXIST) {
406 throw std::runtime_error("Failed to create directory: " + current_path);
407 }
408 }
409 }
410
411#endif // __cplusplus >= 201703L
412
413#endif // defined(__EMSCRIPTEN__)
414
415}; // namespace logit
416
417#endif // _LOGIT_PATH_UTILS_HPP_INCLUDED
The primary namespace for the LogIt++ library.
std::vector< std::string > get_list_files(const std::string &path)
Recursively retrieves a list of all files in a directory.
std::string make_relative(const std::string &file_path, const std::string &base_path)
Computes the relative path from base_path to file_path.
bool is_file(const std::string &path)
Checks if a path represents a file (by checking for an extension).
PathComponents split_path(const std::string &path)
Splits a path into its root and components.
void create_directories(const std::string &path)
Creates directories recursively for the given path.
std::string get_file_name(const std::string &file_path)
Extracts the file name from a full file path.
std::string get_exec_dir()
Retrieves the directory of the executable file.
Structure to hold the root and components of a path.
std::string root
The root part of the path (e.g., "/", "C:")
std::vector< std::string > components
The components of the path.