1// LAF Base Library
2// Copyright (c) 2021-2022 Igara Studio S.A.
3// Copyright (c) 2001-2018 David Capello
4//
5// This file is released under the terms of the MIT license.
6// Read LICENSE.txt for more information.
7
8#ifdef HAVE_CONFIG_H
9#include "config.h"
10#endif
11
12#include "base/fs.h"
13#include "base/split_string.h"
14#include "base/string.h"
15#include "base/utf8_decode.h"
16
17#if LAF_WINDOWS
18 #include "base/fs_win32.h"
19#else
20 #include "base/fs_unix.h"
21#endif
22
23#include <algorithm>
24#include <cctype>
25#include <cstdlib>
26#include <iterator>
27
28namespace base {
29
30#if LAF_WINDOWS
31 const std::string::value_type path_separator = '\\';
32#else
33 const std::string::value_type path_separator = '/';
34#endif
35
36void make_all_directories(const std::string& path)
37{
38 std::vector<std::string> parts;
39 split_string(path, parts, "/\\");
40
41 std::string intermediate;
42 for (const std::string& component : parts) {
43 if (component.empty()) {
44 if (intermediate.empty())
45 intermediate += "/";
46 continue;
47 }
48
49 intermediate = join_path(intermediate, component);
50
51 if (is_file(intermediate))
52 throw std::runtime_error("Error creating directory (a component is a file name)");
53 else if (!is_directory(intermediate))
54 make_directory(intermediate);
55 }
56}
57
58std::string get_absolute_path(const std::string& filename)
59{
60 std::string fn = filename;
61 if (fn.size() > 2 &&
62#if LAF_WINDOWS
63 fn[1] != ':'
64#else
65 fn[0] != '/'
66#endif
67 ) {
68 fn = base::join_path(base::get_current_path(), fn);
69 }
70 fn = base::get_canonical_path(fn);
71 return fn;
72}
73
74bool is_path_separator(std::string::value_type chr)
75{
76 return (chr == '\\' || chr == '/');
77}
78
79std::string get_file_path(const std::string& filename)
80{
81 std::string::const_reverse_iterator rit;
82 std::string res;
83
84 for (rit=filename.rbegin(); rit!=filename.rend(); ++rit)
85 if (is_path_separator(*rit))
86 break;
87
88 if (rit != filename.rend()) {
89 ++rit;
90 std::copy(filename.begin(), std::string::const_iterator(rit.base()),
91 std::back_inserter(res));
92 }
93
94 return res;
95}
96
97std::string get_file_name(const std::string& filename)
98{
99 std::string::const_reverse_iterator rit;
100 std::string result;
101
102 for (rit=filename.rbegin(); rit!=filename.rend(); ++rit)
103 if (is_path_separator(*rit))
104 break;
105
106 std::copy(std::string::const_iterator(rit.base()), filename.end(),
107 std::back_inserter(result));
108
109 return result;
110}
111
112std::string get_file_extension(const std::string& filename)
113{
114 std::string::const_reverse_iterator rit;
115 std::string result;
116
117 // search for the first dot from the end of the string
118 for (rit=filename.rbegin(); rit!=filename.rend(); ++rit) {
119 if (is_path_separator(*rit))
120 return result;
121 else if (*rit == '.')
122 break;
123 }
124
125 if (rit != filename.rend()) {
126 std::copy(std::string::const_iterator(rit.base()), filename.end(),
127 std::back_inserter(result));
128 }
129
130 return result;
131}
132
133std::string replace_extension(const std::string& filename, const std::string& extension)
134{
135 std::string::const_reverse_iterator rit;
136 std::string result;
137
138 // Search for the first dot from the end of the string.
139 for (rit=filename.rbegin(); rit!=filename.rend(); ++rit) {
140 // Here is the dot of the extension.
141 if (*rit == '.')
142 break;
143 // A path separator before a dot, i.e. the filename doesn't have a
144 // extension.
145 else if (is_path_separator(*rit)) {
146 rit = filename.rend();
147 break;
148 }
149 }
150
151 if (rit != filename.rend()) {
152 auto it = std::string::const_iterator(rit.base());
153 --it;
154 std::copy(filename.begin(), it,
155 std::back_inserter(result));
156 }
157 else {
158 result = filename;
159 }
160
161 if (!extension.empty()) {
162 result.push_back('.');
163 result += extension;
164 }
165
166 return result;
167}
168
169
170std::string get_file_title(const std::string& filename)
171{
172 std::string::const_reverse_iterator rit;
173 std::string::const_iterator last_dot = filename.end();
174 std::string result;
175
176 for (rit=filename.rbegin(); rit!=filename.rend(); ++rit) {
177 if (is_path_separator(*rit))
178 break;
179 else if (*rit == '.' && last_dot == filename.end())
180 last_dot = rit.base()-1;
181 }
182
183 for (std::string::const_iterator it(rit.base()); it!=filename.end(); ++it) {
184 if (it == last_dot)
185 break;
186 else
187 result.push_back(*it);
188 }
189
190 return result;
191}
192
193std::string get_file_title_with_path(const std::string& filename)
194{
195 std::string::const_reverse_iterator rit;
196
197 // search for the first dot from the end of the string
198 for (rit=filename.rbegin(); rit!=filename.rend(); ++rit) {
199 if (is_path_separator(*rit))
200 return filename;
201 else if (*rit == '.')
202 break;
203 }
204
205 if (rit != filename.rend())
206 return filename.substr(0, rit.base() - filename.begin() - 1);
207 else
208 return filename;
209}
210
211std::string join_path(const std::string& path, const std::string& file)
212{
213 std::string result(path);
214
215 // Add a separator at the end if it is necessay
216 if (!result.empty() && !is_path_separator(*(result.end()-1)))
217 result.push_back(path_separator);
218
219 // Add the file
220 result += file;
221 return result;
222}
223
224std::string remove_path_separator(const std::string& path)
225{
226 std::string result(path);
227
228 // Erase all trailing separators
229 while (!result.empty() && is_path_separator(*(result.end()-1)))
230 result.erase(result.end()-1);
231
232 return result;
233}
234
235std::string fix_path_separators(const std::string& filename)
236{
237 std::string result(filename);
238
239 // Replace any separator with the system path separator.
240 std::replace_if(result.begin(), result.end(),
241 is_path_separator, path_separator);
242
243 return result;
244}
245
246std::string normalize_path(const std::string& filename)
247{
248 std::string fn = base::get_canonical_path(filename);
249 fn = base::fix_path_separators(fn);
250 return fn;
251}
252
253bool has_file_extension(const std::string& filename, const base::paths& extensions)
254{
255 if (!filename.empty()) {
256 const std::string ext = get_file_extension(filename);
257 for (const auto& e : extensions)
258 if (utf8_icmp(ext, e) == 0)
259 return true;
260 }
261 return false;
262}
263
264int compare_filenames(const std::string& a, const std::string& b)
265{
266 utf8_decode a_dec(a), b_dec(b);
267
268 while (!a_dec.is_end() && !b_dec.is_end()) {
269 int a_chr = a_dec.next();
270 if (!a_chr)
271 break;
272
273 int b_chr = b_dec.next();
274 if (!b_chr)
275 break;
276
277 if ((a_chr >= '0') && (a_chr <= '9') && (b_chr >= '0') && (b_chr <= '9')) {
278 auto a_dec2 = a_dec;
279 auto b_dec2 = b_dec;
280
281 int a_num = (a_chr - '0');
282 while (int c = a_dec2.next()) {
283 if ((c >= '0') && (c <= '9'))
284 a_num = (a_num*10 + (c - '0'));
285 else
286 break;
287 }
288
289 int b_num = (b_chr - '0');
290 while (int c = b_dec2.next()) {
291 if ((c >= '0') && (c <= '9'))
292 b_num = (b_num*10 + (c - '0'));
293 else
294 break;
295 }
296
297 if (a_num != b_num)
298 return a_num - b_num < 0 ? -1: 1;
299 }
300 else if (is_path_separator(a_chr) && is_path_separator(b_chr)) {
301 // Go to next char
302 }
303 else {
304 a_chr = std::tolower(a_chr);
305 b_chr = std::tolower(b_chr);
306
307 if (a_chr != b_chr)
308 return a_chr - b_chr < 0 ? -1: 1;
309 }
310 }
311
312 if (a_dec.is_end() && b_dec.is_end())
313 return 0;
314 else if (a_dec.is_end())
315 return -1;
316 else
317 return 1;
318}
319
320} // namespace base
321