Pyrogenesis  trunk
path.h
Go to the documentation of this file.
1 /* Copyright (C) 2022 Wildfire Games.
2  *
3  * Permission is hereby granted, free of charge, to any person obtaining
4  * a copy of this software and associated documentation files (the
5  * "Software"), to deal in the Software without restriction, including
6  * without limitation the rights to use, copy, modify, merge, publish,
7  * distribute, sublicense, and/or sell copies of the Software, and to
8  * permit persons to whom the Software is furnished to do so, subject to
9  * the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included
12  * in all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21  */
22 
23 /*
24  * Path string class, similar to boost::filesystem::basic_path.
25  */
26 
27 // notes:
28 // - this module is independent of lib/file so that it can be used from
29 // other code without pulling in the entire file manager.
30 // - there is no restriction on buffer lengths except the underlying OS.
31 // input buffers must not exceed PATH_MAX chars, while outputs
32 // must hold at least that much.
33 // - unless otherwise mentioned, all functions are intended to work with
34 // native and VFS paths.
35 // when reading, both '/' and SYS_DIR_SEP are accepted; '/' is written.
36 
37 #ifndef INCLUDED_PATH
38 #define INCLUDED_PATH
39 
40 #include "lib/sysdep/os.h"
41 #include "lib/utf8.h"
42 
43 #include <algorithm>
44 #include <cstring>
45 #if OS_WIN
46 #include <filesystem>
47 #endif
48 #include <functional>
49 
50 namespace ERR
51 {
52  const Status PATH_CHARACTER_ILLEGAL = -100300;
53  const Status PATH_CHARACTER_UNSAFE = -100301;
54  const Status PATH_NOT_FOUND = -100302;
55  const Status PATH_MIXED_SEPARATORS = -100303;
56 }
57 
58 /**
59  * is s2 a subpath of s1, or vice versa? (equal counts as subpath)
60  *
61  * @param s1, s2 comparand strings
62  * @return bool
63  **/
64 bool path_is_subpath(const wchar_t* s1, const wchar_t* s2);
65 
66 /**
67  * Get the path component of a path.
68  * Skips over all characters up to the last dir separator, if any.
69  *
70  * @param path Input path.
71  * @return pointer to path component within <path>.
72  **/
73 const wchar_t* path_name_only(const wchar_t* path);
74 
75 
76 // NB: there is a need for 'generic' paths (e.g. for Trace entry / archive pathnames).
77 // converting between specialized variants via c_str would be inefficient, and the
78 // Os/VfsPath types are hopefully sufficient to avoid errors.
79 class Path
80 {
81 public:
82  using String = std::wstring;
83 
84  Path()
85  {
86  DetectSeparator();
87  }
88 
89  Path(const Path& p)
90  : path(p.path)
91  {
92  DetectSeparator();
93  }
94 
95  Path(const char* p)
96  : path((const unsigned char*)p, (const unsigned char*)p+strlen(p))
97  // interpret bytes as unsigned; makes no difference for ASCII,
98  // and ensures OsPath on Unix will only contain values 0 <= c < 0x100
99  {
100  DetectSeparator();
101  }
102 
103  Path(const wchar_t* p)
104  : path(p, p+wcslen(p))
105  {
106  DetectSeparator();
107  }
108 
109  Path(const std::string& s)
110  : path((const unsigned char*)s.c_str(), (const unsigned char*)s.c_str()+s.length())
111  {
112  DetectSeparator();
113  }
114 
115  Path(const std::wstring& s)
116  : path(s)
117  {
118  DetectSeparator();
119  }
120 
121  Path& operator=(const Path& rhs)
122  {
123  path = rhs.path;
124  DetectSeparator(); // (warns if separators differ)
125  return *this;
126  }
127 
128  bool empty() const
129  {
130  return path.empty();
131  }
132 
133  // TODO: This macro should be removed later when macOS supports std::filesystem.
134  // Currently it does in more recent SDKs, but it also causes a slowdown on
135  // OpenGL. See #6193.
136 #if OS_WIN
137  /**
138  * @returns a STL version of the path.
139  */
140  std::filesystem::path fileSystemPath() const
141  {
142  return std::filesystem::path(path);
143  }
144 #endif
145 
146  const String& string() const
147  {
148  return path;
149  }
150 
151  /**
152  * Return a UTF-8 version of the path, in a human-readable but potentially
153  * lossy form. It is *not* safe to take this string and construct a new
154  * Path object from it (it may fail for some non-ASCII paths) - it should
155  * only be used for displaying paths to users.
156  */
157  std::string string8() const
158  {
159  Status err;
160 #if !OS_WIN
161  // On Unix, assume paths consisting of 8-bit charactes saved in this wide string.
162  std::string spath(path.begin(), path.end());
163 
164  // Return it if it's valid UTF-8
165  wstring_from_utf8(spath, &err);
166  if(err == INFO::OK)
167  return spath;
168 
169  // Otherwise assume ISO-8859-1 and let utf8_from_wstring treat each character as a Unicode code point.
170 #endif
171  // On Windows, paths are UTF-16 strings. We don't support non-BMP characters so we can assume it's simply a wstring.
172  return utf8_from_wstring(path, &err);
173  }
174 
175  bool operator<(const Path& rhs) const
176  {
177  return path < rhs.path;
178  }
179 
180  bool operator==(const Path& rhs) const
181  {
182  return path == rhs.path;
183  }
184 
185  bool operator!=(const Path& rhs) const
186  {
187  return !operator==(rhs);
188  }
189 
190  bool IsDirectory() const
191  {
192  if(empty()) // (ensure length()-1 is safe)
193  return true; // (the VFS root directory is represented as an empty string)
194  return path[path.length()-1] == separator;
195  }
196 
197  Path Parent() const
198  {
199  const size_t idxSlash = path.find_last_of(separator);
200  if(idxSlash == String::npos)
201  return L"";
202  return path.substr(0, idxSlash);
203  }
204 
205  Path Filename() const
206  {
207  const size_t idxSlash = path.find_last_of(separator);
208  if(idxSlash == String::npos)
209  return path;
210  return path.substr(idxSlash+1);
211  }
212 
213  Path Basename() const
214  {
215  const Path filename = Filename();
216  const size_t idxDot = filename.string().find_last_of('.');
217  if(idxDot == String::npos)
218  return filename;
219  return filename.string().substr(0, idxDot);
220  }
221 
222  // (Path return type allows callers to use our operator==)
223  Path Extension() const
224  {
225  const Path filename = Filename();
226  const size_t idxDot = filename.string().find_last_of('.');
227  if(idxDot == String::npos)
228  return Path();
229  return filename.string().substr(idxDot);
230  }
231 
233  {
234  return Parent() / Path(Basename().string() + extension.string());
235  }
236 
237  Path operator/(Path rhs) const
238  {
239  Path ret = *this;
240  if(ret.path.empty()) // (empty paths assume '/')
241  ret.separator = rhs.separator;
242  if(!ret.IsDirectory())
243  ret.path += ret.separator;
244 
245  if(rhs.path.find((ret.separator == '/')? '\\' : '/') != String::npos)
246  {
247  PrintToDebugOutput();
248  rhs.PrintToDebugOutput();
250  }
251  ret.path += rhs.path;
252  return ret;
253  }
254 
255  /**
256  * Return the path before the common part of both paths
257  * @param other Indicates the start of the path which should be removed
258  * @note other should be a VfsPath, while this should be an OsPath
259  */
260  Path BeforeCommon(Path other) const
261  {
262  Path ret = *this;
263  if(ret.empty() || other.empty())
264  return L"";
265 
266  // Convert the separator to allow for string comparison
267  if(other.separator != ret.separator)
268  std::replace(other.path.begin(), other.path.end(), other.separator, ret.separator);
269 
270  const size_t idx = ret.path.rfind(other.path);
271  if(idx == String::npos)
272  return L"";
273 
274  return path.substr(0, idx);
275  }
276 
277  static Status Validate(String::value_type c);
278 
279 private:
280  void PrintToDebugOutput() const
281  {
282  debug_printf("Path %s, separator %c\n", string8().c_str(), (char)separator);
283  }
284 
286  {
287  const size_t idxBackslash = path.find('\\');
288 
289  if(path.find('/') != String::npos && idxBackslash != String::npos)
290  {
291  PrintToDebugOutput();
293  }
294 
295  // (default to '/' for empty strings)
296  separator = (idxBackslash == String::npos)? '/' : '\\';
297  }
298 
300 
301  // note: ideally, path strings would only contain '/' or even SYS_DIR_SEP.
302  // however, Windows-specific code (e.g. the sound driver detection)
303  // uses these routines with '\\' strings. the boost::filesystem approach of
304  // converting them all to '/' and then back via external_file_string is
305  // annoying and inefficient. we allow either type of separators,
306  // appending whichever was first encountered. when modifying the path,
307  // we ensure the same separator is used.
308  wchar_t separator = L'/';
309 };
310 
311 static inline std::wostream& operator<<(std::wostream& s, const Path& path)
312 {
313  s << path.string();
314  return s;
315 }
316 
317 static inline std::wistream& operator>>(std::wistream& s, Path& path)
318 {
319  Path::String string;
320  s >> string;
321  path = Path(string);
322  return s;
323 }
324 
325 namespace std
326 {
327 template<>
328 struct hash<Path>
329 {
330  std::size_t operator()(const Path& path) const
331  {
332  return m_StringHash(path.string());
333  }
334 
335 private:
336  std::hash<std::wstring> m_StringHash;
337 };
338 }
339 
340 #endif // #ifndef INCLUDED_PATH
Path operator/(Path rhs) const
Definition: path.h:237
void DetectSeparator()
Definition: path.h:285
std::hash< std::wstring > m_StringHash
Definition: path.h:336
const String & string() const
Definition: path.h:146
const Status OK
Definition: status.h:384
std::string utf8_from_wstring(const std::wstring &src, Status *err)
opposite of wstring_from_utf8
Definition: utf8.cpp:212
const Status PATH_CHARACTER_ILLEGAL
Definition: path.h:52
bool operator==(const FCDJointWeightPair &a, const FCDJointWeightPair &b)
Definition: GeomReindex.cpp:59
Path(const char *p)
Definition: path.h:95
Definition: ShaderDefines.cpp:30
Path ChangeExtension(Path extension) const
Definition: path.h:232
bool path_is_subpath(const wchar_t *s1, const wchar_t *s2)
is s2 a subpath of s1, or vice versa? (equal counts as subpath)
Definition: path.cpp:51
Path Filename() const
Definition: path.h:205
const Status PATH_CHARACTER_UNSAFE
Definition: path.h:53
wchar_t separator
Definition: path.h:308
std::size_t operator()(const Path &path) const
Definition: path.h:330
bool operator!=(const Path &rhs) const
Definition: path.h:185
Path Basename() const
Definition: path.h:213
Definition: path.h:79
String path
Definition: path.h:299
Path(const wchar_t *p)
Definition: path.h:103
const wchar_t * path_name_only(const wchar_t *path)
Get the path component of a path.
Definition: path.cpp:85
Path Extension() const
Definition: path.h:223
bool empty() const
Definition: path.h:128
i64 Status
Error handling system.
Definition: status.h:169
bool operator==(const Path &rhs) const
Definition: path.h:180
const Status PATH_MIXED_SEPARATORS
Definition: path.h:55
void debug_printf(const char *fmt,...)
write a formatted string to the debug channel, subject to filtering (see below).
Definition: debug.cpp:148
Path(const std::wstring &s)
Definition: path.h:115
Path(const std::string &s)
Definition: path.h:109
Path BeforeCommon(Path other) const
Return the path before the common part of both paths.
Definition: path.h:260
std::wstring String
Definition: path.h:82
#define DEBUG_WARN_ERR(status)
display the error dialog with text corresponding to the given error code.
Definition: debug.h:339
std::wstring wstring_from_utf8(const std::string &src, Status *err)
convert UTF-8 to a wide string (UTF-16 or UCS-4, depending on the platform&#39;s wchar_t).
Definition: utf8.cpp:229
Introduction
Definition: debug.h:407
const char * extension
Definition: mongoose.cpp:1741
static std::wistream & operator>>(std::wistream &s, Path &path)
Definition: path.h:317
Path(const Path &p)
Definition: path.h:89
bool IsDirectory() const
Definition: path.h:190
Path & operator=(const Path &rhs)
Definition: path.h:121
void PrintToDebugOutput() const
Definition: path.h:280
Path Parent() const
Definition: path.h:197
Path()
Definition: path.h:84
std::string string8() const
Return a UTF-8 version of the path, in a human-readable but potentially lossy form.
Definition: path.h:157
static std::wostream & operator<<(std::wostream &s, const Path &path)
Definition: path.h:311
const Status PATH_NOT_FOUND
Definition: path.h:54
bool operator<(const Path &rhs) const
Definition: path.h:175