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