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#elif defined(__APPLE__)
21// For macOS systems
22#include <mach-o/dyld.h>
23#include <stdlib.h>
24#include <unistd.h>
25#include <limits.h>
26#include <dirent.h>
27#include <sys/stat.h>
28#include <errno.h>
29#else
30// For other POSIX systems
31#include <unistd.h>
32#include <limits.h>
33#include <dirent.h>
34#include <sys/stat.h>
35#include <errno.h>
36#endif
37
38namespace logit {
39# if __cplusplus >= 201703L
40 namespace fs = std::filesystem;
41# endif
42
43#if defined(__EMSCRIPTEN__)
44
45 inline std::string get_exec_dir() { return "./"; }
46
47 inline std::vector<std::string> get_list_files(const std::string&) = delete;
48
49 inline std::string get_file_name(const std::string& file_path) {
50 size_t pos = file_path.find_last_of("/\\");
51 if (pos == std::string::npos) return file_path;
52 return file_path.substr(pos + 1);
53 }
54
55 inline std::string make_relative(const std::string& file_path, const std::string&) {
56 return file_path;
57 }
58
59 inline void create_directories(const std::string&) = delete;
60
61 inline bool is_file(const std::string& path) {
62 size_t dot_pos = path.find_last_of('.');
63 size_t slash_pos = path.find_last_of("/\\");
64 return (dot_pos != std::string::npos && (slash_pos == std::string::npos || dot_pos > slash_pos));
65 }
66
67#else
68
71 std::string get_exec_dir() {
72# ifdef _WIN32
73 std::vector<wchar_t> buffer(MAX_PATH);
74 HMODULE hModule = GetModuleHandle(NULL);
75
76 // Try to get the path
77 std::size_t size = static_cast<std::size_t>(GetModuleFileNameW(hModule, buffer.data(), static_cast<DWORD>(buffer.size())));
78
79 // If the path is too long, increase the buffer size
80 while (size == buffer.size() && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
81 buffer.resize(buffer.size() * 2); // Double the buffer size
82 size = static_cast<std::size_t>(GetModuleFileNameW(hModule, buffer.data(), static_cast<DWORD>(buffer.size())));
83 }
84
85 if (size == 0) {
86 throw std::runtime_error("Failed to get executable path.");
87 }
88
89 std::wstring exe_path(buffer.begin(), buffer.begin() + size);
90
91 // Trim the path to the directory (remove the file name, keep only the folder path)
92 size_t pos = exe_path.find_last_of(L"\\/");
93 if (pos != std::wstring::npos) {
94 exe_path = exe_path.substr(0, pos);
95 }
96
97 // Convert from std::wstring (UTF-16) to std::string (UTF-8)
98 return wstring_to_utf8(exe_path);
99# elif defined(__APPLE__)
100 uint32_t size = 0;
101 _NSGetExecutablePath(nullptr, &size);
102 std::vector<char> path(size);
103 if (_NSGetExecutablePath(path.data(), &size) != 0) {
104 throw std::runtime_error("Failed to get executable path.");
105 }
106 std::string exe_path(path.data());
107 char resolved[PATH_MAX];
108 if (realpath(exe_path.c_str(), resolved) == nullptr) {
109 throw std::runtime_error("Failed to resolve executable path.");
110 }
111 exe_path = resolved;
112 size_t pos = exe_path.find_last_of("\\/");
113 if (pos != std::string::npos) {
114 exe_path = exe_path.substr(0, pos);
115 }
116 return exe_path;
117# else
118 char result[PATH_MAX];
119 ssize_t count = readlink("/proc/self/exe", result, PATH_MAX);
120
121 if (count == -1) {
122 throw std::runtime_error("Failed to get executable path.");
123 }
124
125 std::string exe_path(result, count);
126
127 // Trim the path to the directory (remove the file name, keep only the folder path)
128 size_t pos = exe_path.find_last_of("\\/");
129 if (pos != std::string::npos) {
130 exe_path = exe_path.substr(0, pos);
131 }
132
133 return exe_path;
134# endif
135 }
136
140 std::vector<std::string> get_list_files(const std::string& path) {
141 std::vector<std::string> list_files;
142# ifdef _WIN32
143 // Use wide versions of functions to correctly handle non-ASCII characters.
144 std::wstring wsearch_path;
145
146 // If the path is empty, use the current directory.
147 if (path.empty()) {
148 wchar_t buffer[MAX_PATH];
149 GetCurrentDirectoryW(MAX_PATH, buffer);
150 wsearch_path = buffer;
151 } else {
152 wsearch_path = utf8_to_wstring(path);
153 }
154
155 // Ensure there is a trailing separator.
156 if (!wsearch_path.empty()) {
157 wchar_t last_char = wsearch_path.back();
158 if (last_char != L'\\' && last_char != L'/') {
159 wsearch_path.push_back(L'\\');
160 }
161 }
162
163 // Create the search pattern.
164 std::wstring pattern = wsearch_path + L"*";
165 WIN32_FIND_DATAW fd;
166 HANDLE hFind = FindFirstFileW(pattern.c_str(), &fd);
167 if (hFind != INVALID_HANDLE_VALUE) {
168 do {
169 if (wcscmp(fd.cFileName, L".") == 0 || wcscmp(fd.cFileName, L"..") == 0)
170 continue;
171
172 std::wstring wfull_path = wsearch_path + fd.cFileName;
173
174 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
175 // Recursively process subdirectories.
176 std::vector<std::string> sub_files = get_list_files(wstring_to_utf8(wfull_path));
177 list_files.insert(list_files.end(), sub_files.begin(), sub_files.end());
178 } else {
179 // Add the found file.
180 list_files.push_back(wstring_to_utf8(wfull_path));
181 }
182 } while (FindNextFileW(hFind, &fd));
183 FindClose(hFind);
184 }
185# else
186 // Implementation for POSIX systems.
187 std::string search_path = path;
188 if (search_path.empty()) {
189 char buffer[PATH_MAX];
190 if (getcwd(buffer, PATH_MAX)) {
191 search_path = buffer;
192 }
193 }
194 // Ensure there is a trailing separator.
195 if (search_path.back() != '/' && search_path.back() != '\\') {
196 search_path.push_back('/');
197 }
198 DIR* dir = opendir(search_path.c_str());
199 if (dir) {
200 struct dirent* entry;
201 while ((entry = readdir(dir)) != nullptr) {
202 std::string file_name = entry->d_name;
203 if (file_name == "." || file_name == "..")
204 continue;
205 std::string full_path = search_path + file_name;
206 struct stat statbuf;
207 if (stat(full_path.c_str(), &statbuf) == 0) {
208 if (S_ISDIR(statbuf.st_mode)) {
209 std::vector<std::string> sub_files = get_list_files(full_path);
210 list_files.insert(list_files.end(), sub_files.begin(), sub_files.end());
211 } else if (S_ISREG(statbuf.st_mode)) {
212 list_files.push_back(full_path);
213 }
214 }
215 }
216 closedir(dir);
217 }
218# endif
219 return list_files;
220 }
221
225 std::string get_file_name(const std::string& file_path) {
226# if __cplusplus >= 201703L
227 return fs::u8path(file_path).filename().u8string();
228# else
229 size_t pos = file_path.find_last_of("/\\");
230 if (pos == std::string::npos) return file_path;
231 return file_path.substr(pos + 1);
232# endif
233 }
234
235#if __cplusplus >= 201703L
236
241 inline std::string make_relative(const std::string& file_path, const std::string& base_path) {
242 if (base_path.empty()) return file_path;
243 std::filesystem::path fileP = std::filesystem::u8path(file_path);
244 std::filesystem::path baseP = std::filesystem::u8path(base_path);
245 std::error_code ec; // For exception-safe operation
246 std::filesystem::path relativeP = std::filesystem::relative(fileP, baseP, ec);
247 if (ec) {
248 // If there is an error, return the original file_path
249 return file_path;
250 } else {
251 return relativeP.u8string();
252 }
253 }
254
258 void create_directories(const std::string& path) {
259# ifdef _WIN32
260 // Convert UTF-8 string to wide string for Windows
261 std::wstring wide_path = utf8_to_wstring(path);
262 std::filesystem::path dir(wide_path);
263# else
264 std::filesystem::path dir = std::filesystem::u8path(path);
265# endif
266 if (!std::filesystem::exists(dir)) {
267 std::error_code ec;
268 if (!std::filesystem::create_directories(dir, ec)) {
269 throw std::runtime_error("Failed to create directories for path: " + dir.u8string());
270 }
271 }
272 }
273
274#else
275
279 std::string root;
280 std::vector<std::string> components;
281 };
282
286 PathComponents split_path(const std::string& path) {
287 PathComponents result;
288 size_t i = 0;
289 size_t n = path.size();
290
291 // Handle root paths for Unix and Windows
292 if (n >= 1 && (path[0] == '/' || path[0] == '\\')) {
293 // Unix root "/"
294 result.root = "/";
295 ++i;
296 } else if (n >= 2 && std::isalpha(path[0]) && path[1] == ':') {
297 // Windows drive letter "C:"
298 result.root = path.substr(0, 2);
299 i = 2;
300 if (n >= 3 && (path[2] == '/' || path[2] == '\\')) {
301 // "C:/"
302 ++i;
303 }
304 }
305
306 // Split the path into components
307 while (i < n) {
308 // Skip path separators
309 while (i < n && (path[i] == '/' || path[i] == '\\')) {
310 ++i;
311 }
312 // Find the next separator
313 size_t j = i;
314 while (j < n && path[j] != '/' && path[j] != '\\') {
315 ++j;
316 }
317 if (i < j) {
318 result.components.push_back(path.substr(i, j - i));
319 i = j;
320 }
321 }
322
323 return result;
324 }
325
330 std::string make_relative(const std::string& file_path, const std::string& base_path) {
331 if (base_path.empty()) return file_path;
332 PathComponents file_pc = split_path(file_path);
333 PathComponents base_pc = split_path(base_path);
334
335 // If roots are different, return the original file_path
336 if (file_pc.root != base_pc.root) {
337 return file_path;
338 }
339
340 // Find the common prefix components
341 size_t common_size = 0;
342 while (common_size < file_pc.components.size() &&
343 common_size < base_pc.components.size() &&
344 file_pc.components[common_size] == base_pc.components[common_size]) {
345 ++common_size;
346 }
347
348 // Build the relative path components
349 std::vector<std::string> relative_components;
350
351 // Add ".." for each remaining component in base path
352 for (size_t i = common_size; i < base_pc.components.size(); ++i) {
353 relative_components.push_back("..");
354 }
355
356 // Add the remaining components from the file path
357 for (size_t i = common_size; i < file_pc.components.size(); ++i) {
358 relative_components.push_back(file_pc.components[i]);
359 }
360
361 // Join the components into a relative path string
362 std::string relative_path;
363 if (relative_components.empty()) {
364 relative_path = ".";
365 } else {
366 for (size_t i = 0; i < relative_components.size(); ++i) {
367 if (i > 0) {
368# ifdef _WIN32
369 relative_path += '\\'; // Windows
370# else
371 relative_path += '/';
372# endif
373 }
374 relative_path += relative_components[i];
375 }
376 }
377
378 return relative_path;
379 }
380
384 inline bool is_file(const std::string& path) {
385 size_t dot_pos = path.find_last_of('.');
386 size_t slash_pos = path.find_last_of("/\\");
387 return (dot_pos != std::string::npos && (slash_pos == std::string::npos || dot_pos > slash_pos));
388 }
389
393 void create_directories(const std::string& path) {
394 if (path.empty()) return;
395 PathComponents path_pc = split_path(path);
396 auto &components = path_pc.components;
397 size_t components_size = components.size();
398
399 // Check if the last component is a file
400 if (is_file(path)) {
401 --components_size;
402 }
403
404 // Build the path incrementally and create directories
405 std::string current_path = path_pc.root;
406 for (size_t i = 0; i < components_size; ++i) {
407 if (!current_path.empty() && current_path.back() != '/' && current_path.back() != '\\') {
408 current_path += '/';
409 }
410 current_path += components[i];
411
412 // Skip special components
413 if (components[i] == ".." ||
414 components[i] == "/" ||
415 components[i] == "~/") continue;
416# ifdef _WIN32
417 int ret = _mkdir(utf8_to_ansi(current_path).c_str());
418# else
419 int ret = mkdir(current_path.c_str(), 0755);
420# endif
421 int errnum = errno;
422 if (ret != 0 && errnum != EEXIST) {
423 throw std::runtime_error("Failed to create directory: " + current_path);
424 }
425 }
426 }
427
428#endif // __cplusplus >= 201703L
429
430#endif // defined(__EMSCRIPTEN__)
431
432}; // namespace logit
433
434#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.