aboutsummaryrefslogtreecommitdiff
path: root/Src/external_dependencies/openmpt-trunk/src/mpt/path/path.hpp
blob: 96f88f85a6d29dbaa41e5b90f767065c8e1cc7b2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */

#ifndef MPT_PATH_PATH_HPP
#define MPT_PATH_PATH_HPP



#include "mpt/base/detect.hpp"
#include "mpt/base/namespace.hpp"
#include "mpt/string/types.hpp"
#include "mpt/string_transcode/transcode.hpp"

#include <filesystem>
#include <type_traits>
#include <utility>

#if MPT_OS_WINDOWS
#include <windows.h>
#endif // MPT_OS_WINDOWS



namespace mpt {
inline namespace MPT_INLINE_NS {



// mpt::os_path is an alias to a string type that represents native operating system path encoding.
// Note that this differs from std::filesystem::path::string_type on both Windows and Posix.
// On Windows, we actually honor UNICODE and thus allow os_path.c_str() to be usable with WinAPI functions.
// On Posix, we use a type-safe string type in locale encoding, in contrast to the encoding-confused supposedly UTF8 std::string in std::filesystem::path.

#if MPT_OS_WINDOWS
using os_path = mpt::winstring;
#else  // !MPT_OS_WINDOWS
using os_path = mpt::lstring;
#endif // MPT_OS_WINDOWS



// mpt::os_path literals that do not involve runtime conversion.

#if MPT_OS_WINDOWS
#define MPT_OSPATH_CHAR(x)    TEXT(x)
#define MPT_OSPATH_LITERAL(x) TEXT(x)
#define MPT_OSPATH(x) \
	mpt::winstring { \
		TEXT(x) \
	}
#else // !MPT_OS_WINDOWS
#define MPT_OSPATH_CHAR(x)    x
#define MPT_OSPATH_LITERAL(x) x
#define MPT_OSPATH(x) \
	mpt::lstring { \
		x \
	}
#endif // MPT_OS_WINDOWS



template <>
struct make_string_type<std::filesystem::path> {
	using type = std::filesystem::path;
};


template <>
struct is_string_type<std::filesystem::path> : public std::true_type { };



template <>
struct string_transcoder<std::filesystem::path> {
	using string_type = std::filesystem::path;
	static inline mpt::widestring decode(const string_type & src) {
		if constexpr (std::is_same<std::filesystem::path::value_type, char>::value) {
			// In contrast to standard recommendation and cppreference,
			// native encoding on unix-like systems with libstdc++ or libc++ is actually *NOT* UTF8,
			// but instead the conventional std::locale::locale("") encoding (which happens to be UTF8 on all modern systems, but this is not guaranteed).
			// Note: libstdc++ and libc++ however assume that their internal representation is UTF8,
			// which implies that wstring/u32string/u16string/u8string conversions are actually broken and MUST NOT be used, ever.
			return mpt::transcode<mpt::widestring>(mpt::logical_encoding::locale, src.string());
#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
		} else if constexpr (std::is_same<std::filesystem::path::value_type, wchar_t>::value) {
			return mpt::transcode<mpt::widestring>(src.wstring());
#endif // !MPT_COMPILER_QUIRK_NO_WCHAR
		} else if constexpr (std::is_same<std::filesystem::path::value_type, char32_t>::value) {
			return mpt::transcode<mpt::widestring>(src.u32string());
		} else if constexpr (std::is_same<std::filesystem::path::value_type, char16_t>::value) {
			return mpt::transcode<mpt::widestring>(src.u16string());
#if MPT_CXX_AT_LEAST(20)
		} else if constexpr (std::is_same<std::filesystem::path::value_type, char8_t>::value) {
			return mpt::transcode<mpt::widestring>(src.u8string());
#endif
		} else {
#if MPT_OS_WINDOWS && !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
			return mpt::transcode<mpt::widestring>(src.wstring());
#elif MPT_OS_WINDOWS
			return mpt::transcode<mpt::widestring>(mpt::logical_encoding::locale, src.string());
#else
			// completely unknown implementation, assume it can sanely convert to/from UTF16/UTF32
			if constexpr (sizeof(mpt::widechar) == sizeof(char32_t)) {
				return mpt::transcode<mpt::widestring>(src.u32string());
			} else {
				return mpt::transcode<mpt::widestring>(src.u16string());
			}
#endif
		}
	}
	static inline string_type encode(const mpt::widestring & src, std::filesystem::path::format fmt) {
		if constexpr (std::is_same<std::filesystem::path::value_type, char>::value) {
			// In contrast to standard recommendation and cppreference,
			// native encoding on unix-like systems with libstdc++ or libc++ is actually *NOT* UTF8,
			// but instead the conventional std::locale::locale("") encoding (which happens to be UTF8 on all modern systems, but this is not guaranteed).
			// Note: libstdc++ and libc++ however assume that their internal representation is UTF8,
			// which implies that wstring/u32string/u16string/u8string conversions are actually broken and MUST NOT be used, ever.
			return std::filesystem::path{mpt::transcode<std::string>(mpt::logical_encoding::locale, src), fmt};
#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
		} else if constexpr (std::is_same<std::filesystem::path::value_type, wchar_t>::value) {
			return std::filesystem::path{mpt::transcode<std::wstring>(src), fmt};
#endif // !MPT_COMPILER_QUIRK_NO_WCHAR
		} else if constexpr (std::is_same<std::filesystem::path::value_type, char32_t>::value) {
			return std::filesystem::path{mpt::transcode<std::u32string>(src), fmt};
		} else if constexpr (std::is_same<std::filesystem::path::value_type, char16_t>::value) {
			return std::filesystem::path{mpt::transcode<std::u16string>(src), fmt};
#if MPT_CXX_AT_LEAST(20)
		} else if constexpr (std::is_same<std::filesystem::path::value_type, char8_t>::value) {
			return std::filesystem::path{mpt::transcode<std::u8string>(src), fmt};
#endif
		} else {
#if MPT_OS_WINDOWS && !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
			return std::filesystem::path{mpt::transcode<std::wstring>(src), fmt};
#elif MPT_OS_WINDOWS
			return std::filesystem::path{mpt::transcode<std::string>(mpt::logical_encoding::locale, src), fmt};
#else
			// completely unknown implementation, assume it can sanely convert to/from UTF16/UTF32
			if constexpr (sizeof(mpt::widechar) == sizeof(char32_t)) {
				return std::filesystem::path{mpt::transcode<std::u32string>(src), fmt};
			} else {
				return std::filesystem::path{mpt::transcode<std::u16string>(src), fmt};
			}
#endif
		}
	}
	static inline string_type encode(const mpt::widestring & src) {
		return encode(src, std::filesystem::path::auto_format);
	}
};



// Best heuristics we can come up with to define std::filesystem::path literals that do not involve (or at least only non-lossy) runtime conversion.

#if MPT_OS_WINDOWS && !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
#define MPT_STDPATH_CHAR(x)    L##x
#define MPT_STDPATH_LITERAL(x) L##x
#define MPT_STDPATH(x) \
	std::filesystem::path { \
		L##x \
	}
#elif MPT_OS_WINDOWS
#define MPT_STDPATH_CHAR(x)    x
#define MPT_STDPATH_LITERAL(x) x
#define MPT_STDPATH(x) \
	std::filesystem::path { \
		x \
	}
#elif MPT_CXX_AT_LEAST(20)
#define MPT_STDPATH_CHAR(x)    u8##x
#define MPT_STDPATH_LITERAL(x) u8##x
#define MPT_STDPATH(x) \
	std::filesystem::path { \
		u8##x \
	}
#else
#define MPT_STDPATH_CHAR(x)    U##x
#define MPT_STDPATH_LITERAL(x) U##x
#define MPT_STDPATH(x) \
	std::filesystem::path { \
		U##x \
	}
#endif



// std::filesystem::path offers implicit conversions to/from types of which it is confused about their encodings.
// The only way to work around this problem is to implement our own mpt::path that does not do such broken nonsense.
// We offer no implicit conversions and only explicit conversions from std::filesystem::path and mpt::os_path.

class path {
public:
	using format = std::filesystem::path::format;
	using std_value_type = std::filesystem::path::value_type;
	using std_string_type = std::filesystem::path;
	static constexpr inline std_value_type std_preferred_separator = std::filesystem::path::preferred_separator;
	using os_value_type = os_path::value_type;
	using os_string_type = os_path;
	static constexpr inline os_value_type os_preferred_separator = static_cast<os_value_type>(std::filesystem::path::preferred_separator);

private:
	std::filesystem::path m_path;

private:
	template <typename path_type, std::enable_if_t<std::is_same<path_type, std::filesystem::path>::value, bool> = true>
	explicit path(const path_type & p)
		: m_path(p) {
		return;
	}
	template <typename path_type, std::enable_if_t<std::is_same<path_type, std::filesystem::path>::value, bool> = true>
	explicit path(path_type && p)
		: m_path(std::forward<path_type>(p)) {
		return;
	}

public:
	struct internal {
		static inline path make_path(std::filesystem::path && p) {
			return path{std::move(p)};
		}
	};

public:
	template <typename path_type, std::enable_if_t<std::is_same<path_type, std::filesystem::path>::value, bool> = true>
	static path from_stdpath(const path_type & p) {
		return path{p};
	}
	static std::filesystem::path to_stdpath(const path & p) {
		return p.m_path;
	}
	static os_path to_ospath(const path & p) {
		return mpt::transcode<os_path>(p.m_path);
	}
	static std::filesystem::path from_ospath(const os_path & s, std::filesystem::path::format fmt = std::filesystem::path::auto_format) {
		return string_transcoder<std::filesystem::path>{}.encode(mpt::transcode<mpt::widestring>(s), fmt);
	}

public:
	path() noexcept = default;
	path(const path & p)
		: m_path(p.m_path) {
		return;
	}
	path(path && p)
		: m_path(std::move(p.m_path)) {
		return;
	}
	explicit path(const os_path & s, std::filesystem::path::format fmt = std::filesystem::path::auto_format)
		: m_path(from_ospath(s, fmt)) {
		return;
	}
	path & operator=(const path & p) {
		m_path = p.m_path;
		return *this;
	}
	path & operator=(path && p) {
		m_path = std::move(p.m_path);
		return *this;
	}
	path & assign(const path & p) {
		m_path = p.m_path;
		return *this;
	}
	path & assign(path && p) {
		m_path = std::move(p.m_path);
		return *this;
	}
	path & operator/=(const path & p) {
		m_path /= p.m_path;
		return *this;
	}
	path & operator/=(path && p) {
		m_path /= std::move(p.m_path);
		return *this;
	}
	// concatenation
	path & append(const path & p) {
		m_path /= p.m_path;
		return *this;
	}
	path & append(path && p) {
		m_path /= std::move(p.m_path);
		return *this;
	}
	path & operator+=(const path & p) {
		m_path += p.m_path;
		return *this;
	}
	path & operator+=(path && p) {
		m_path += std::move(p.m_path);
		return *this;
	}
	path & concat(const path & p) {
		m_path += p.m_path;
		return *this;
	}
	path & concat(path && p) {
		m_path += std::move(p.m_path);
		return *this;
	}
	// modifiers
	void clear() noexcept {
		m_path.clear();
	}
	path & make_preferred() {
		m_path.make_preferred();
		return *this;
	}
	path & remove_filename() {
		m_path.remove_filename();
		return *this;
	}
	path & replace_filename(const path & replacement) {
		m_path.replace_filename(replacement.m_path);
		return *this;
	}
	path & replace_extension(const path & replacement = path()) {
		m_path.replace_extension(replacement.m_path);
		return *this;
	}
	void swap(path & other) {
		m_path.swap(other.m_path);
	}
	// format observers
	std::filesystem::path stdpath() const {
		return m_path;
	}
	os_path ospath() const {
		return to_ospath(*this);
	}
	// compare
	int compare(const path & p) const noexcept {
		return m_path.compare(p.m_path);
	}
	// generation
	path lexically_normal() const {
		return path{m_path.lexically_normal()};
	}
	path lexically_relative(const path & base) const {
		return path{m_path.lexically_relative(base.m_path)};
	}
	path lexically_proximate(const path & base) const {
		return path{m_path.lexically_proximate(base.m_path)};
	}
	// decomposition
	path root_name() const {
		return path{m_path.root_name()};
	}
	path root_directory() const {
		return path{m_path.root_directory()};
	}
	path root_path() const {
		return path{m_path.root_path()};
	}
	path relative_path() const {
		return path{m_path.relative_path()};
	}
	path parent_path() const {
		return path{m_path.parent_path()};
	}
	path filename() const {
		return path{m_path.filename()};
	}
	path stem() const {
		return path{m_path.stem()};
	}
	path extension() const {
		return path{m_path.extension()};
	}
	// queries
	[[nodiscard]] bool empty() const noexcept {
		return m_path.empty();
	}
	bool has_root_path() const {
		return m_path.has_root_path();
	}
	bool has_root_name() const {
		return m_path.has_root_name();
	}
	bool has_root_directory() const {
		return m_path.has_root_directory();
	}
	bool has_relative_path() const {
		return m_path.has_relative_path();
	}
	bool has_parent_path() const {
		return m_path.has_parent_path();
	}
	bool has_filename() const {
		return m_path.has_filename();
	}
	bool has_stem() const {
		return m_path.has_stem();
	}
	bool has_extension() const {
		return m_path.has_extension();
	}
	bool is_absolute() const {
		return m_path.is_absolute();
	}
	bool is_relative() const {
		return m_path.is_relative();
	}
	// comparison operators
	friend bool operator==(const path & lhs, const path & rhs) noexcept {
		return lhs.m_path == rhs.m_path;
	}
	friend bool operator!=(const path & lhs, const path & rhs) noexcept {
		return lhs.m_path != rhs.m_path;
	}
	friend bool operator<(const path & lhs, const path & rhs) noexcept {
		return lhs.m_path < rhs.m_path;
	}
	friend bool operator<=(const path & lhs, const path & rhs) noexcept {
		return lhs.m_path <= rhs.m_path;
	}
	friend bool operator>(const path & lhs, const path & rhs) noexcept {
		return lhs.m_path > rhs.m_path;
	}
	friend bool operator>=(const path & lhs, const path & rhs) noexcept {
		return lhs.m_path >= rhs.m_path;
	}
	// copncatenation operator
	friend path operator/(const path & lhs, const path & rhs) {
		return path{lhs.m_path / rhs.m_path};
	}
};



// Best heuristics we can come up with to define mpt::path literals that do not involve (or at least only non-lossy) runtime conversion.

#if MPT_OS_WINDOWS && !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
#define MPT_PATH_CHAR(x)    L##x
#define MPT_PATH_LITERAL(x) L##x
#define MPT_PATH(x)         mpt::path::internal::make_path(L##x)
#elif MPT_OS_WINDOWS
#define MPT_PATH_CHAR(x)    x
#define MPT_PATH_LITERAL(x) x
#define MPT_PATH(x)         mpt::path::internal::make_path(x)
#elif MPT_CXX_AT_LEAST(20)
#define MPT_PATH_CHAR(x)    u8##x
#define MPT_PATH_LITERAL(x) u8##x
#define MPT_PATH(x)         mpt::path::internal::make_path(u8##x)
#else
#define MPT_PATH_CHAR(x)    U##x
#define MPT_PATH_LITERAL(x) U##x
#define MPT_PATH(x)         mpt::path::internal::make_path(U##x)
#endif



template <>
struct make_string_type<mpt::path> {
	using type = mpt::path;
};


template <>
struct is_string_type<mpt::path> : public std::true_type { };



template <>
struct string_transcoder<mpt::path> {
	using string_type = mpt::path;
	static inline mpt::widestring decode(const string_type & src) {
		return mpt::transcode<mpt::widestring>(src.ospath());
	}
	static inline string_type encode(const mpt::widestring & src) {
		return mpt::path{mpt::transcode<mpt::os_path>(src)};
	}
};



inline mpt::os_path support_long_path(const mpt::os_path & path) {
#if MPT_OS_WINDOWS
	if (path.length() < MAX_PATH) {
		// path is short enough
		return path;
	}
	if (path.substr(0, 4) == MPT_OSPATH_LITERAL("\\\\?\\")) {
		// path is already in prefixed form
		return path;
	}
	const mpt::os_path absolute_path = mpt::transcode<mpt::os_path>(std::filesystem::absolute(mpt::transcode<std::filesystem::path>(path)));
	if (absolute_path.substr(0, 2) == MPT_OSPATH_LITERAL("\\\\")) {
		// Path is a network share: \\server\foo.bar -> \\?\UNC\server\foo.bar
		return MPT_OSPATH_LITERAL("\\\\?\\UNC") + absolute_path.substr(1);
	} else {
		// Regular file: C:\foo.bar -> \\?\C:\foo.bar
		return MPT_OSPATH_LITERAL("\\\\?\\") + absolute_path;
	}
#else
	return path;
#endif
}



} // namespace MPT_INLINE_NS
} // namespace mpt



#endif // MPT_PATH_PATH_HPP