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