Euphoria
vfs_path.cc
Go to the documentation of this file.
1 #include "io/vfs_path.h"
2 
3 #include <algorithm>
4 #include "assert/assert.h"
5 
6 #include "base/cint.h"
7 #include "base/stringutils.h"
8 #include "base/stringmerger.h"
9 
10 
11 namespace eu::io
12 {
13  namespace
14  {
15  bool
16  is_valid_directory_name(const std::string& dir)
17  {
18  if(dir.empty()) { return false; }
19  if(dir == ".") { return true; }
20  if(dir == "..") { return true; }
21  if(dir.find('.') != std::string::npos) { return false; }
22  if(dir.find('/') != std::string::npos) { return false; }
23  return true;
24  }
25 
26  bool
27  is_valid_first_directory(const std::string& dir)
28  {
29  if(dir == "~") { return true; }
30  if(dir == ".") { return true; }
31  return false;
32  }
33 
34  bool
35  is_valid_filename(const std::string& file)
36  {
37  if(file.empty()) { return false; }
38  if(file.find('/') != std::string::npos) { return false; }
39  if(file == ".") { return false; }
40  return true;
41  }
42  }
43 
44 
45  std::optional<FilePath>
46  FilePath::from_script(const std::string& p)
47  {
48  if(p.find('\\') != std::string::npos)
49  {
50  return std::nullopt;
51  }
52  if(p.find(':') != std::string::npos)
53  {
54  return std::nullopt;
55  }
56  if(begins_with(p, "~/") || begins_with(p, "./"))
57  {
58  return FilePath{p};
59  }
60  else
61  {
62  if(begins_with(p, "/"))
63  {
64  return FilePath{"." + p};
65  }
66  else
67  {
68  return FilePath{"./" + p};
69  }
70  }
71  }
72 
73 
74  std::optional<FilePath>
75  FilePath::from_dirty_source(const std::string& p)
76  {
77  std::string s = trim(p);
78  if(s.size() < 4)
79  {
80  return std::nullopt;
81  }
82  // slashes
83  s = replace_with_character(s, "\\", '/');
84 
85  // C:\style paths
86  if(s.substr(1, 2) == ":/")
87  {
88  s = s.substr(1);
89  s[0] = '.';
90  }
91 
92  // \\share\style paths
93  if(s.substr(0, 2) == "//")
94  {
95  s[0] = '.';
96  }
97  return from_script(s);
98  }
99 
100 
101  std::optional<FilePath>
102  FilePath::from_script_or_empty(const std::string& path)
103  {
104  if(path.empty())
105  {
106  return std::nullopt;
107  }
108  else
109  {
110  return from_script(path);
111  }
112  }
113 
114 
115  std::tuple<DirPath, std::string>
117  {
118  const auto slash = path.rfind('/');
119  ASSERTX(slash != std::string::npos, path);
120  const auto dir_part = path.substr(0, slash+1);
121  const auto file_part = path.substr(slash+1);
122  ASSERTX
123  (
124  !file_part.empty() && !dir_part.empty(),
125  path, dir_part, file_part
126  );
127  ASSERTX
128  (
129  *dir_part.rbegin() == '/' && file_part[0] != '/',
130  path, dir_part, file_part
131  );
132  return {DirPath{dir_part}, file_part};
133  }
134 
135 
136  DirPath
138  {
139  return std::get<0>(split_directories_and_file());
140  }
141 
142 
143  std::string
145  {
146  return std::get<1>(split_directories_and_file());
147  }
148 
149 
150  std::string
152  {
153  const auto with_extension = get_file_with_extension();
154  const auto dot = with_extension.find('.', 1);
155  if(dot == std::string::npos) { return with_extension; }
156  return with_extension.substr(0, dot);
157  }
158 
159 
160  std::string
162  {
163  const auto with_extension = get_file_with_extension();
164  const auto dot = with_extension.find('.', 1);
165  if(dot == std::string::npos) { return ""; }
166  return with_extension.substr(dot+1);
167  }
168 
169  FilePath
170  FilePath::set_extension_copy(const std::string& ext) const
171  {
172  ASSERT(!ext.empty());
173  ASSERTX(ext[0]!='.' && *ext.rbegin()!='.', ext);
174  const auto slash = path.rfind('/');
175  ASSERTX(slash != std::string::npos, path, ext);
176  const auto dot = path.find('.', slash);
177  if(dot == std::string::npos)
178  {
179  return FilePath{path + "." + ext};
180  }
181  else
182  {
183  return FilePath{path.substr(0, dot+1) + ext};
184  }
185  }
186 
187 
188  FilePath
189  FilePath::extend_extension_copy(const std::string& ext) const
190  {
191  ASSERT(!ext.empty());
192  ASSERTX(ext[0]!='.' && *ext.rbegin()!='.', ext);
193  const auto old_ext = get_extension();
194  if(old_ext.empty())
195  {
196  return set_extension_copy(ext);
197  }
198  else
199  {
200  return set_extension_copy(old_ext + "." + ext);
201  }
202  }
203 
204 
205  FilePath::FilePath(const std::string& p)
206  : path(p)
207  {
208  ASSERTX(path.size() > 3, path);
209  ASSERTX((path[0] == '~' || path[0] == '.') && path[1] == '/', path);
210  ASSERTX(*path.rbegin() != '/', path);
211  }
212 
213 
214  // --------------------------------------------------------------------
215 
216 
217  DirPath
219  {
220  return DirPath{"~/"};
221  }
222 
223 
224  DirPath
226  {
227  return DirPath{"./"};
228  }
229 
230 
231  DirPath
232  DirPath::from_dirs(const std::vector<std::string>& dirs)
233  {
234  ASSERT(!dirs.empty());
235  ASSERTX(is_valid_first_directory(dirs[0]), dirs[0]);
236  for(std::size_t index=1; index<dirs.size(); index +=1)
237  {
238  ASSERTX(is_valid_directory_name(dirs[index]), dirs[index]);
239  }
240 
241  return DirPath
242  {
243  StringMerger{}
244  .set_separator("/")
245  .set_start_and_end("", "/")
246  .merge(dirs)
247  };
248  }
249 
250 
251  FilePath
252  DirPath::get_file(const std::string& filename) const
253  {
254  ASSERTX(is_valid_filename(filename), path, filename);
255  return FilePath{path + filename};
256  }
257 
258 
259  bool
261  {
262  ASSERT(!path.empty());
263  const auto first = path[0];
264  ASSERTX(first == '.' || first == '~', first);
265  return first == '.';
266  }
267 
268 
269  bool
271  {
272  const auto is_relative = [](const std::string& dir) -> bool
273  {
274  if(dir == ".") { return true; }
275  if(dir == "..") { return true; }
276 
277  return false;
278  };
279 
280  const auto dirs = split_directories();
281  return std::any_of(dirs.begin(), dirs.end(), is_relative);
282  }
283 
284 
285  DirPath
287  {
288  auto dirs = split_directories();
289  ASSERTX
290  (
291  !dirs.empty(),
293  );
294  dirs.pop_back();
295  return DirPath::from_dirs(dirs);
296  }
297 
298 
299  std::string
301  {
302  const auto dirs = split_directories();
303  ASSERTX(dirs.size() > 1, string_mergers::array.merge(dirs));
304  return *dirs.rbegin();
305  }
306 
307 
308  std::vector<std::string>
310  {
311  auto r = split(path, '/');
312 
313  // if path ends with / then remove the empty "folder"
314  if(r.empty() == false && r.rbegin()->empty())
315  {
316  r.pop_back();
317  }
318  return r;
319  }
320 
321  std::string to_string(const FilePath& p)
322  { return p.path; }
323 
324  std::string to_string(const DirPath& p)
325  { return p.path; }
326 
327 
328  DirPath
329  DirPath::get_directory(const std::string& single) const
330  {
331  ASSERTX(is_valid_directory_name(single), single);
332  return DirPath{path + single + "/"};
333  }
334 
335 
336  DirPath::DirPath(const std::string& p)
337  : path(p)
338  {
339  ASSERT(!path.empty());
340  ASSERTX(is_valid_first_directory(split_directories()[0]), path);
341  ASSERTX(*path.rbegin() == '/', path);
342  }
343 
344 
345  // --------------------------------------------------------------------
346 
347 
348  std::optional<DirPath>
350  {
351  ASSERT(!base.is_relative());
352 
353  auto dirs = base.split_directories();
354  auto size = dirs.size();
355 
356  if(size == 1) { return base; }
357 
358  for(std::size_t index=1; index<size;)
359  {
360  if(dirs[index] != "..")
361  {
362  index += 1;
363  }
364  else
365  {
366  // if it is the first directory, then this is an error
367  // since we can't back out of the root and the
368  // path is actually invalid, like:
369  // ~/../outside_root/
370  if(index == 1) { return std::nullopt; }
371 
372  // drop .. and the parent folder from the path
373  dirs.erase
374  (
375  std::next(dirs.begin(), c_sizet_to_int(index)-1),
376  std::next(dirs.begin(), c_sizet_to_int(index)+1)
377  );
378  index -= 1;
379  size -= 2;
380  }
381  }
382 
383  return DirPath::from_dirs(dirs);
384  }
385 
386 
387  std::optional<DirPath>
388  resolve_relative(const DirPath& base, const DirPath& root)
389  {
390  ASSERTX(root.is_relative() == false, root);
391  const auto path
392  = base.is_relative()
393  ? join(root, base)
394  : base;
395  return resolve_relative(path);
396  }
397 
398 
399  std::optional<FilePath>
401  {
402  const auto [dir, file] = base.split_directories_and_file();
403  const auto resolved = resolve_relative(dir);
404  if(!resolved.has_value()) { return std::nullopt; }
405  return resolved.value().get_file(file);
406  }
407 
408  std::optional<FilePath>
409  resolve_relative(const FilePath& base, const DirPath& root)
410  {
411  const auto [dir, file] = base.split_directories_and_file();
412  const auto resolved = resolve_relative(dir, root);
413  if(!resolved.has_value()) { return std::nullopt; }
414  return resolved.value().get_file(file);
415  }
416 
417 
418  DirPath
419  join(const DirPath& lhs, const DirPath& rhs)
420  {
421  ASSERT(rhs.is_relative());
422  const auto dirs = rhs.split_directories();
423 
424  auto ret = lhs;
425  for(size_t index = 1; index < dirs.size(); index +=1 )
426  {
427  ret = ret.get_directory(dirs[index]);
428  }
429 
430  return ret;
431  }
432 
433 
434  FilePath
435  join(const DirPath& lhs, const FilePath& rhs)
436  {
437  const auto [dir, file] = rhs.split_directories_and_file();
438  const auto joined = join(lhs, dir);
439  return joined.get_file(file);
440  }
441 
442 
443  bool
444  operator==(const DirPath& lhs, const DirPath& rhs)
445  {
446  return lhs.path == rhs.path;
447  }
448 
449 
450  bool
451  operator==(const FilePath& lhs, const FilePath& rhs)
452  {
453  return lhs.path == rhs.path;
454  }
455 
456 
457  bool
458  operator!=(const DirPath& lhs, const DirPath& rhs)
459  {
460  return lhs.path != rhs.path;
461  }
462 
463 
464  bool
465  operator!=(const FilePath& lhs, const FilePath& rhs)
466  {
467  return lhs.path != rhs.path;
468  }
469 
470 
471  bool
472  operator<(const DirPath& lhs, const DirPath& rhs)
473  {
474  return lhs.path < rhs.path;
475  }
476 
477 
478  bool
479  operator<(const FilePath& lhs, const FilePath& rhs)
480  {
481  return lhs.path < rhs.path;
482  }
483 
484 }
ParserBase * base
Definition: argparse.cc:887
#define ASSERTX(x,...)
Definition: assert.h:48
#define ASSERT(x)
Definition: assert.h:29
std::vector< std::string > split(const std::string &s, char delim)
Definition: stringutils.cc:431
std::string replace_with_character(const std::string &string, const std::string &to_find, char to_replace)
Definition: stringutils.cc:301
std::string trim(const std::string &string_to_trim, std::string_view trim_characters)
Remove characters from both the start and the end.
Definition: stringutils.cc:79
bool begins_with(const std::string &string_to_test, const std::string &start)
Tests if a string starts with another string.
Definition: stringutils.cc:87
Definition: enum.h:8
std::string to_string(const FilePath &p)
Definition: vfs_path.cc:321
bool operator!=(const DirPath &lhs, const DirPath &rhs)
Definition: vfs_path.cc:458
std::optional< DirPath > resolve_relative(const DirPath &base)
Definition: vfs_path.cc:349
bool operator<(const DirPath &lhs, const DirPath &rhs)
Definition: vfs_path.cc:472
DirPath join(const DirPath &lhs, const DirPath &rhs)
Definition: vfs_path.cc:419
bool operator==(const DirPath &lhs, const DirPath &rhs)
Definition: vfs_path.cc:444
constexpr StringMerger array
Definition: stringmerger.h:91
int c_sizet_to_int(size_t t)
Definition: cint.cc:11
float dot(const quatf &lhs, const quatf &rhs)
Definition: quat.cc:363
String utility functions.
constexpr StringMerger & set_start_and_end(const std::string_view &both, const std::string_view &the_end)
Definition: stringmerger.h:47
std::string merge(const std::vector< std::string > &strings) const
Definition: stringmerger.cc:11
constexpr StringMerger & set_separator(const std::string_view &the_separator, const std::string_view &the_final_separator)
Definition: stringmerger.h:24
DirPath get_directory(const std::string &single) const
Definition: vfs_path.cc:329
bool is_relative() const
Definition: vfs_path.cc:260
DirPath(const std::string &p)
Definition: vfs_path.cc:336
std::string get_name() const
Definition: vfs_path.cc:300
DirPath get_parent_directory() const
Definition: vfs_path.cc:286
static DirPath from_dirs(const std::vector< std::string > &dirs)
Definition: vfs_path.cc:232
std::string path
contains either . or ~ at the start, / at the end
Definition: vfs_path.h:69
bool contains_relative() const
Definition: vfs_path.cc:270
static DirPath from_root()
Definition: vfs_path.cc:218
static DirPath from_relative()
Definition: vfs_path.cc:225
std::vector< std::string > split_directories() const
Definition: vfs_path.cc:309
FilePath get_file(const std::string &filename) const
Definition: vfs_path.cc:252
FilePath extend_extension_copy(const std::string &ext) const
Definition: vfs_path.cc:189
std::tuple< DirPath, std::string > split_directories_and_file() const
Definition: vfs_path.cc:116
static std::optional< FilePath > from_script_or_empty(const std::string &path)
optional or not, log if error
Definition: vfs_path.cc:102
FilePath set_extension_copy(const std::string &ext) const
Definition: vfs_path.cc:170
DirPath get_directory() const
Definition: vfs_path.cc:137
std::string path
contains either .
Definition: vfs_path.h:39
std::string get_file_with_extension() const
Definition: vfs_path.cc:144
std::string get_extension() const
Definition: vfs_path.cc:161
std::string get_filename_without_extension() const
Definition: vfs_path.cc:151
static std::optional< FilePath > from_script(const std::string &path)
apply only minor changes, return null on invalid
Definition: vfs_path.cc:46
static std::optional< FilePath > from_dirty_source(const std::string &path)
do everything possible to convert from dirty path to valid path
Definition: vfs_path.cc:75
FilePath(const std::string &p)
Definition: vfs_path.cc:205