1 /**
2 	Contains routines for high level path handling.
3 
4 	Copyright: © 2012-2021 Sönke Ludwig
5 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6 	Authors: Sönke Ludwig
7 */
8 module vibe.core.path;
9 
10 import std.algorithm.searching : commonPrefix, endsWith, startsWith;
11 import std.algorithm.comparison : equal, min;
12 import std.algorithm.iteration : map;
13 import std.exception : enforce;
14 import std.range : empty, front, popFront, popFrontExactly, takeExactly;
15 import std.range.primitives : ElementType, isInputRange, isOutputRange, isForwardRange, save;
16 import std.traits : isArray, isInstanceOf, isSomeChar;
17 import std.utf : byChar;
18 
19 
20 /** Computes the relative path from `base_path` to this path.
21 
22 	Params:
23 		path = The destination path
24 		base_path = The path from which the relative path starts
25 
26 	See_also: `relativeToWeb`
27 */
28 Path relativeTo(Path)(Path path, Path base_path) @safe
29 	if (isInstanceOf!(GenericPath, Path))
30 {
31 	import std.array : array, replicate;
32 	import std.range : chain, drop, take;
33 
34 	assert(base_path.absolute, "Base path must be absolute for relativeTo.");
35 	assert(path.absolute, "Path must be absolute for relativeTo.");
36 
37 	if (is(Path == WindowsPath)) { // FIXME: this shouldn't be a special case here!
38 		bool samePrefix(size_t n)
39 		{
40 			return path.bySegment2.map!(n => n.encodedName).take(n).equal(base_path.bySegment2.map!(n => n.encodedName).take(n));
41 		}
42 		// a path such as ..\C:\windows is not valid, so force the path to stay absolute in this case
43 		auto pref = path.bySegment2;
44 		if (!pref.empty && pref.front.encodedName == "") {
45 			pref.popFront();
46 			if (!pref.empty) {
47 				// different drive?
48 				if (pref.front.encodedName.endsWith(':') && !samePrefix(2))
49 					return path;
50 				// different UNC path?
51 				if (pref.front.encodedName == "" && !samePrefix(4))
52 					return path;
53 			}
54 		}
55 	}
56 
57 	auto nodes = path.bySegment2;
58 	auto base_nodes = base_path.bySegment2;
59 
60 	// skip and count common prefix
61 	size_t base = 0;
62 	while (!nodes.empty && !base_nodes.empty && equal(nodes.front.name, base_nodes.front.name)) {
63 		nodes.popFront();
64 		base_nodes.popFront();
65 		base++;
66 	}
67 
68 	enum up = Path.Segment2("..", Path.defaultSeparator);
69 	auto ret = Path(base_nodes.map!(p => up).chain(nodes));
70 	if (path.endsWithSlash) {
71 		if (ret.empty) return Path.fromTrustedString("." ~ path.toString()[$-1]);
72 		else ret.endsWithSlash = true;
73 	}
74 	return ret;
75 }
76 
77 ///
78 unittest {
79 	import std.array : array;
80 	import std.conv : to;
81 	assert(PosixPath("/some/path").relativeTo(PosixPath("/")) == PosixPath("some/path"));
82 	assert(PosixPath("/some/path/").relativeTo(PosixPath("/some/other/path/")) == PosixPath("../../path/"));
83 	assert(PosixPath("/some/path/").relativeTo(PosixPath("/some/other/path")) == PosixPath("../../path/"));
84 
85 	assert(WindowsPath("C:\\some\\path").relativeTo(WindowsPath("C:\\")) == WindowsPath("some\\path"));
86 	assert(WindowsPath("C:\\some\\path\\").relativeTo(WindowsPath("C:\\some\\other\\path/")) == WindowsPath("..\\..\\path\\"));
87 	assert(WindowsPath("C:\\some\\path\\").relativeTo(WindowsPath("C:\\some\\other\\path")) == WindowsPath("..\\..\\path\\"));
88 
89 	assert(WindowsPath("\\\\server\\share\\some\\path").relativeTo(WindowsPath("\\\\server\\share\\")) == WindowsPath("some\\path"));
90 	assert(WindowsPath("\\\\server\\share\\some\\path\\").relativeTo(WindowsPath("\\\\server\\share\\some\\other\\path/")) == WindowsPath("..\\..\\path\\"));
91 	assert(WindowsPath("\\\\server\\share\\some\\path\\").relativeTo(WindowsPath("\\\\server\\share\\some\\other\\path")) == WindowsPath("..\\..\\path\\"));
92 
93 	assert(WindowsPath("C:\\some\\path").relativeTo(WindowsPath("D:\\")) == WindowsPath("C:\\some\\path"));
94 	assert(WindowsPath("C:\\some\\path\\").relativeTo(WindowsPath("\\\\server\\share")) == WindowsPath("C:\\some\\path\\"));
95 	assert(WindowsPath("\\\\server\\some\\path\\").relativeTo(WindowsPath("C:\\some\\other\\path")) == WindowsPath("\\\\server\\some\\path\\"));
96 	assert(WindowsPath("\\\\server\\some\\path\\").relativeTo(WindowsPath("\\\\otherserver\\path")) == WindowsPath("\\\\server\\some\\path\\"));
97 	assert(WindowsPath("\\some\\path\\").relativeTo(WindowsPath("\\other\\path")) == WindowsPath("..\\..\\some\\path\\"));
98 
99 	assert(WindowsPath("\\\\server\\share\\path1").relativeTo(WindowsPath("\\\\server\\share\\path2")) == WindowsPath("..\\path1"));
100 	assert(WindowsPath("\\\\server\\share\\path1").relativeTo(WindowsPath("\\\\server\\share2\\path2")) == WindowsPath("\\\\server\\share\\path1"));
101 	assert(WindowsPath("\\\\server\\share\\path1").relativeTo(WindowsPath("\\\\server2\\share2\\path2")) == WindowsPath("\\\\server\\share\\path1"));
102 }
103 
104 unittest {
105 	{
106 		auto parentpath = "/path/to/parent";
107 		auto parentpathp = PosixPath(parentpath);
108 		auto subpath = "/path/to/parent/sub/";
109 		auto subpathp = PosixPath(subpath);
110 		auto subpath_rel = "sub/";
111 		assert(subpathp.relativeTo(parentpathp).toString() == subpath_rel);
112 		auto subfile = "/path/to/parent/child";
113 		auto subfilep = PosixPath(subfile);
114 		auto subfile_rel = "child";
115 		assert(subfilep.relativeTo(parentpathp).toString() == subfile_rel);
116 	}
117 
118 	{ // relative paths across Windows devices are not allowed
119 		auto p1 = WindowsPath("\\\\server\\share"); assert(p1.absolute);
120 		auto p2 = WindowsPath("\\\\server\\othershare"); assert(p2.absolute);
121 		auto p3 = WindowsPath("\\\\otherserver\\share"); assert(p3.absolute);
122 		auto p4 = WindowsPath("C:\\somepath"); assert(p4.absolute);
123 		auto p5 = WindowsPath("C:\\someotherpath"); assert(p5.absolute);
124 		auto p6 = WindowsPath("D:\\somepath"); assert(p6.absolute);
125 		auto p7 = WindowsPath("\\\\server\\share\\path"); assert(p7.absolute);
126 		auto p8 = WindowsPath("\\\\server\\share\\otherpath"); assert(p8.absolute);
127 		assert(p4.relativeTo(p5) == WindowsPath("..\\somepath"));
128 		assert(p4.relativeTo(p6) == WindowsPath("C:\\somepath"));
129 		assert(p4.relativeTo(p1) == WindowsPath("C:\\somepath"));
130 		assert(p1.relativeTo(p2) == WindowsPath("\\\\server\\share"));
131 		assert(p1.relativeTo(p3) == WindowsPath("\\\\server\\share"));
132 		assert(p1.relativeTo(p4) == WindowsPath("\\\\server\\share"));
133 		assert(p7.relativeTo(p1) == WindowsPath("path"));
134 		assert(p7.relativeTo(p8) == WindowsPath("..\\path"));
135 	}
136 
137 	{ // relative path, trailing slash
138 		auto p1 = PosixPath("/some/path");
139 		auto p2 = PosixPath("/some/path/");
140 		assert(p1.relativeTo(p1).toString() == "");
141 		assert(p1.relativeTo(p2).toString() == "");
142 		assert(p2.relativeTo(p2).toString() == "./");
143 	}
144 }
145 
146 nothrow unittest {
147 	auto p1 = PosixPath.fromTrustedString("/foo/bar/baz");
148 	auto p2 = PosixPath.fromTrustedString("/foo/baz/bam");
149 	assert(p2.relativeTo(p1).toString == "../../baz/bam");
150 }
151 
152 
153 /** Computes the relative path to this path from `base_path` using web path rules.
154 
155 	The difference to `relativeTo` is that a path not ending in a slash
156 	will not be considered as a path to a directory and the parent path
157 	will instead be used.
158 
159 	Params:
160 		path = The destination path
161 		base_path = The path from which the relative path starts
162 
163 	See_also: `relativeTo`
164 */
165 Path relativeToWeb(Path)(Path path, Path base_path) @safe
166 	if (isInstanceOf!(GenericPath, Path))
167 {
168 	if (!base_path.endsWithSlash) {
169 		assert(base_path.absolute, "Base path must be absolute for relativeToWeb.");
170 		if (base_path.hasParentPath) base_path = base_path.parentPath;
171 		else base_path = Path("/");
172 		assert(base_path.absolute);
173 	}
174 	return path.relativeTo(base_path);
175 }
176 
177 ///
178 /+unittest {
179 	assert(InetPath("/some/path").relativeToWeb(InetPath("/")) == InetPath("some/path"));
180 	assert(InetPath("/some/path/").relativeToWeb(InetPath("/some/other/path/")) == InetPath("../../path/"));
181 	assert(InetPath("/some/path/").relativeToWeb(InetPath("/some/other/path")) == InetPath("../path/"));
182 }+/
183 
184 
185 /** Converts a path to its system native string representation.
186 */
187 string toNativeString(P)(P path)
188 {
189 	return (cast(NativePath)path).toString();
190 }
191 
192 
193 /// Represents a path on Windows operating systems.
194 alias WindowsPath = GenericPath!WindowsPathFormat;
195 
196 /// Represents a path on Unix/Posix systems.
197 alias PosixPath = GenericPath!PosixPathFormat;
198 
199 /// Represents a path as part of an URI.
200 alias InetPath = GenericPath!InetPathFormat;
201 
202 /// The path type native to the target operating system.
203 version (Windows) alias NativePath = WindowsPath;
204 else alias NativePath = PosixPath;
205 
206 deprecated("Use NativePath or one the specific path types instead.")
207 alias Path = NativePath;
208 deprecated("Use NativePath.Segment or one the specific path types instead.")
209 alias PathEntry = Path.Segment;
210 
211 /// Provides a common interface to operate on paths of various kinds.
212 struct GenericPath(F) {
213 @safe:
214 	alias Format = F;
215 
216 	/** A single path segment.
217 	*/
218 	static struct Segment {
219 		@safe:
220 
221 		private {
222 			string m_name;
223 			char m_separator = 0;
224 		}
225 
226 		/** Constructs a new path segment including an optional trailing
227 			separator.
228 
229 			Params:
230 				name = The raw (unencoded) name of the path segment
231 				separator = Optional trailing path separator (e.g. `'/'`)
232 
233 			Throws:
234 				A `PathValidationException` is thrown if the name contains
235 				characters that are invalid for the path type. In particular,
236 				any path separator characters may not be part of the name.
237 		*/
238 		this(string name, char separator = '\0')
239 		{
240 			import std.algorithm.searching : any;
241 
242 			enforce!PathValidationException(separator == '\0' || Format.isSeparator(separator),
243 				"Invalid path separator.");
244 			auto err = Format.validateDecodedSegment(name);
245 			enforce!PathValidationException(err is null, err);
246 
247 			m_name = name;
248 			m_separator = separator;
249 		}
250 
251 		/** Constructs a path segment without performing validation.
252 
253 			Note that in debug builds, there are still assertions in place
254 			that verify that the provided values are valid.
255 
256 			Params:
257 				name = The raw (unencoded) name of the path segment
258 				separator = Optional trailing path separator (e.g. `'/'`)
259 		*/
260 		static Segment fromTrustedString(string name, char separator = '\0')
261 		nothrow @nogc pure {
262 			import std.algorithm.searching : any;
263 			assert(separator == '\0' || Format.isSeparator(separator));
264 			assert(Format.validateDecodedSegment(name) is null, "Invalid path segment.");
265 
266 			Segment ret;
267 			ret.m_name = name;
268 			ret.m_separator = separator;
269 			return ret;
270 		}
271 
272 		deprecated("Use the constructor instead.")
273 		static Segment validateFilename(string name)
274 		{
275 			return Segment(name);
276 		}
277 
278 		/// The (file/directory) name of the path segment.
279 		@property string name() const nothrow @nogc { return m_name; }
280 		/// The trailing separator (e.g. `'/'`) or `'\0'`.
281 		@property char separator() const nothrow @nogc { return m_separator; }
282 		/// ditto
283 		@property void separator(char ch) {
284 			enforce!PathValidationException(ch == '\0' || Format.isSeparator(ch),
285 				"Character is not a valid path separator.");
286 			m_separator = ch;
287 		}
288 		/// Returns `true` $(I iff) the segment has a trailing path separator.
289 		@property bool hasSeparator() const nothrow @nogc { return m_separator != '\0'; }
290 
291 		deprecated("Use .name instead.")
292 		string toString() const nothrow @nogc { return m_name; }
293 
294 		/** Converts the segment to another path type.
295 
296 			The segment name will be re-validated during the conversion. The
297 			separator, if any, will be adopted or replaced by the default
298 			separator of the target path type.
299 
300 			Throws:
301 				A `PathValidationException` is thrown if the segment name cannot
302 				be represented in the target path format.
303 		*/
304 		GenericPath!F.Segment opCast(T : GenericPath!F.Segment, F)()
305 		const {
306 			char dsep = '\0';
307 			if (m_separator) {
308 				if (F.isSeparator(m_separator)) dsep = m_separator;
309 				else dsep = F.defaultSeparator;
310 			}
311 			return GenericPath!F.Segment(m_name, dsep);
312 		}
313 
314 		/// Compares two path segment names
315 		bool opEquals(Segment other) const nothrow @nogc { return this.name == other.name && this.hasSeparator == other.hasSeparator; }
316 		/// ditto
317 		bool opEquals(string name) const nothrow @nogc { return this.name == name; }
318 	}
319 
320 	/** Represents a path as an forward range of `Segment`s.
321 	*/
322 	static struct PathRange {
323 		import std.traits : ReturnType;
324 
325 		private {
326 			string m_path;
327 			Segment m_front;
328 		}
329 
330 		private this(string path)
331 		{
332 			m_path = path;
333 			if (m_path.length) {
334 				auto ap = Format.getAbsolutePrefix(m_path);
335 				if (ap.length && !Format.isSeparator(ap[0]))
336 					m_front = Segment.fromTrustedString(null, Format.defaultSeparator);
337 				else readFront();
338 			}
339 		}
340 
341 		@property bool empty() const nothrow @nogc { return m_path.length == 0 && m_front == Segment.init; }
342 
343 		@property PathRange save() { return this; }
344 
345 		@property Segment front() { return m_front; }
346 
347 		void popFront()
348 		nothrow {
349 			assert(m_front != Segment.init);
350 			if (m_path.length) readFront();
351 			else m_front = Segment.init;
352 		}
353 
354 		private void readFront()
355 		nothrow {
356 			import std.array : array;
357 
358 			auto n = Format.getFrontNode(m_path);
359 			m_path = m_path[n.length .. $];
360 
361 			char sep = '\0';
362 			if (Format.isSeparator(n[$-1])) {
363 				sep = n[$-1];
364 				n = n[0 .. $-1];
365 			}
366 			static if (is(typeof(Format.decodeSingleSegment(n)) == string))
367 				string ndec = Format.decodeSingleSegment(n);
368 			else
369 				string ndec = Format.decodeSingleSegment(n).array;
370 			m_front = Segment.fromTrustedString(ndec, sep);
371 			assert(m_front != Segment.init);
372 		}
373 	}
374 
375 	/** A single path segment.
376 	*/
377 	static struct Segment2 {
378 		@safe:
379 
380 		private {
381 			string m_encodedName;
382 			char m_separator = 0;
383 		}
384 
385 		/** Constructs a new path segment including an optional trailing
386 			separator.
387 
388 			Params:
389 				name = The raw (unencoded) name of the path segment
390 				separator = Optional trailing path separator (e.g. `'/'`)
391 
392 			Throws:
393 				A `PathValidationException` is thrown if the name contains
394 				characters that are invalid for the path type. In particular,
395 				any path separator characters may not be part of the name.
396 		*/
397 		this(string name, char separator = '\0')
398 		{
399 			import std.algorithm.searching : any;
400 
401 			enforce!PathValidationException(separator == '\0' || Format.isSeparator(separator),
402 				"Invalid path separator.");
403 			auto err = Format.validateDecodedSegment(name);
404 			enforce!PathValidationException(err is null, err);
405 
406 			m_encodedName = Format.encodeSegment(name);
407 			m_separator = separator;
408 		}
409 
410 		/** Constructs a path segment without performing validation.
411 
412 			Note that in debug builds, there are still assertions in place
413 			that verify that the provided values are valid.
414 
415 			Params:
416 				name = The raw (unencoded) name of the path segment
417 				separator = Optional trailing path separator (e.g. `'/'`)
418 		*/
419 		static Segment2 fromTrustedString(string name, char separator = '\0')
420 		nothrow pure {
421 			import std.algorithm.searching : any;
422 			assert(separator == '\0' || Format.isSeparator(separator));
423 			assert(Format.validateDecodedSegment(name) is null, "Invalid path segment.");
424 			return fromTrustedEncodedString(Format.encodeSegment(name), separator);
425 		}
426 
427 		/** Constructs a path segment without performing validation.
428 
429 			Note that in debug builds, there are still assertions in place
430 			that verify that the provided values are valid.
431 
432 			Params:
433 				encoded_name = The encoded name of the path segment
434 				separator = Optional trailing path separator (e.g. `'/'`)
435 		*/
436 		static Segment2 fromTrustedEncodedString(string encoded_name, char separator = '\0')
437 		nothrow @nogc pure {
438 			import std.algorithm.searching : any;
439 			import std.utf : byCodeUnit;
440 
441 			assert(separator == '\0' || Format.isSeparator(separator));
442 			assert(!encoded_name.byCodeUnit.any!(c => Format.isSeparator(c)));
443 			assert(Format.validatePath(encoded_name) is null, "Invalid path segment.");
444 
445 			Segment2 ret;
446 			ret.m_encodedName = encoded_name;
447 			ret.m_separator = separator;
448 			return ret;
449 		}
450 
451 		/** The (file/directory) name of the path segment.
452 
453 			Note: Depending on the path type, this may return a generic range
454 			type instead of `string`. Use `name.to!string` in that
455 			case if you need an actual `string`.
456 		*/
457 		@property auto name() const nothrow @nogc { return Format.decodeSingleSegment(m_encodedName); }
458 		/// The encoded representation of the path segment name
459 		@property string encodedName() const nothrow @nogc { return m_encodedName; }
460 		/// The trailing separator (e.g. `'/'`) or `'\0'`.
461 		@property char separator() const nothrow @nogc { return m_separator; }
462 		/// ditto
463 		@property void separator(char ch) {
464 			enforce!PathValidationException(ch == '\0' || Format.isSeparator(ch),
465 				"Character is not a valid path separator.");
466 			m_separator = ch;
467 		}
468 		/// Returns `true` $(I iff) the segment has a trailing path separator.
469 		@property bool hasSeparator() const nothrow @nogc { return m_separator != '\0'; }
470 
471 
472 		/** The extension part of the file name.
473 
474 			If the file name contains an extension, this returns a forward range
475 			with the extension including the leading dot. Otherwise an empty
476 			range is returned.
477 
478 			See_also: `stripExtension`
479 		*/
480 		@property auto extension()
481 		const nothrow @nogc {
482 			return .extension(this.name);
483 		}
484 
485 		///
486 		unittest {
487 			assert(PosixPath("/foo/bar.txt").head2.extension.equal(".txt"));
488 			assert(PosixPath("/foo/bar").head2.extension.equal(""));
489 			assert(PosixPath("/foo/.bar").head2.extension.equal(""));
490 			assert(PosixPath("/foo/.bar.txt").head2.extension.equal(".txt"));
491 		}
492 
493 
494 		/** Returns the file base name, excluding the extension.
495 
496 			See_also: `extension`
497 		*/
498 		@property auto withoutExtension()
499 		const nothrow @nogc {
500 			return .stripExtension(this.name);
501 		}
502 
503 		///
504 		unittest {
505 			assert(PosixPath("/foo/bar.txt").head2.withoutExtension.equal("bar"));
506 			assert(PosixPath("/foo/bar").head2.withoutExtension.equal("bar"));
507 			assert(PosixPath("/foo/.bar").head2.withoutExtension.equal(".bar"));
508 			assert(PosixPath("/foo/.bar.txt").head2.withoutExtension.equal(".bar"));
509 		}
510 
511 
512 		/** Converts the segment to another path type.
513 
514 			The segment name will be re-validated during the conversion. The
515 			separator, if any, will be adopted or replaced by the default
516 			separator of the target path type.
517 
518 			Throws:
519 				A `PathValidationException` is thrown if the segment name cannot
520 				be represented in the target path format.
521 		*/
522 		GenericPath!F.Segment2 opCast(T : GenericPath!F.Segment2, F)()
523 		const {
524 			import std.array : array;
525 
526 			char dsep = '\0';
527 			if (m_separator) {
528 				if (F.isSeparator(m_separator)) dsep = m_separator;
529 				else dsep = F.defaultSeparator;
530 			}
531 			static if (is(typeof(this.name) == string))
532 				string n = this.name;
533 			else
534 				string n = this.name.array;
535 			return GenericPath!F.Segment2(n, dsep);
536 		}
537 
538 		/// Compares two path segment names
539 		bool opEquals(Segment2 other)
540 		const nothrow @nogc {
541 			try return equal(this.name, other.name) && this.hasSeparator == other.hasSeparator;
542 			catch (Exception e) assert(false, e.msg);
543 		}
544 		/// ditto
545 		bool opEquals(string name)
546 		const nothrow @nogc {
547 			import std.utf : byCodeUnit;
548 			try return equal(this.name, name.byCodeUnit);
549 			catch (Exception e) assert(false, e.msg);
550 		}
551 	}
552 
553 	private {
554 		string m_path;
555 	}
556 
557 	/// The default path segment separator character.
558 	enum char defaultSeparator = Format.defaultSeparator;
559 
560 	/** Constructs a path from its string representation.
561 
562 		Throws:
563 			A `PathValidationException` is thrown if the given path string
564 			is not valid.
565 	*/
566 	this(string p)
567 	{
568 		auto err = Format.validatePath(p);
569 		enforce!PathValidationException(err is null, err);
570 		m_path = p;
571 	}
572 
573 	/** Constructs a path from a single path segment.
574 
575 		This is equivalent to calling the range based constructor with a
576 		single-element range.
577 	*/
578 	this(Segment segment)
579 	{
580 		import std.range : only;
581 		this(only(segment));
582 	}
583 	/// ditto
584 	this(Segment2 segment)
585 	{
586 		import std.range : only;
587 		this(only(segment));
588 	}
589 
590 	/** Constructs a path from an input range of `Segment`s.
591 
592 		Throws:
593 			Since path segments are pre-validated, this constructor does not
594 			throw an exception.
595 	*/
596 	this(R)(R segments)
597 		if (isInputRange!R && is(ElementType!R : Segment))
598 	{
599 		import std.array : appender;
600 		auto dst = appender!string;
601 		Format.toString(segments, dst);
602 		m_path = dst.data;
603 	}
604 	/// ditto
605 	this(R)(R segments)
606 		if (isInputRange!R && is(ElementType!R : Segment2))
607 	{
608 		import std.array : appender;
609 		auto dst = appender!string;
610 		Format.toString(segments, dst);
611 		m_path = dst.data;
612 	}
613 
614 	/** Constructs a path from its string representation.
615 
616 		This is equivalent to calling the string based constructor.
617 	*/
618 	static GenericPath fromString(string p)
619 	{
620 		return GenericPath(p);
621 	}
622 
623 	/** Constructs a path from its string representation, skipping the
624 		validation.
625 
626 		Note that it is required to pass a pre-validated path string
627 		to this function. Debug builds will enforce this with an assertion.
628 	*/
629 	static GenericPath fromTrustedString(string p)
630 	nothrow @nogc {
631 		assert(Format.validatePath(p) is null, "Invalid trusted path.");
632 		GenericPath ret;
633 		ret.m_path = p;
634 		return ret;
635 	}
636 
637 	/// Tests if a certain character is a path segment separator.
638 	static bool isSeparator(dchar ch) { return ch < 0x80 && Format.isSeparator(cast(char)ch); }
639 
640 	/// Tests if the path is represented by an empty string.
641 	@property bool empty() const nothrow @nogc { return m_path.length == 0; }
642 
643 	/// Tests if the path is absolute.
644 	@property bool absolute() const nothrow @nogc { return Format.getAbsolutePrefix(m_path).length > 0; }
645 
646 	/// Determines whether the path ends with a path separator (i.e. represents a folder specifically).
647 	@property bool endsWithSlash() const nothrow @nogc { return m_path.length > 0 && Format.isSeparator(m_path[$-1]); }
648 	/// ditto
649 	@property void endsWithSlash(bool v)
650 	nothrow {
651 		bool ews = this.endsWithSlash;
652 		if (!ews && v) m_path ~= Format.defaultSeparator;
653 		else if (ews && !v) m_path = m_path[0 .. $-1]; // FIXME?: "/test//" -> "/test/"
654 	}
655 
656 	/// Iterates over the path by `Segment`.
657 	@property PathRange bySegment() const { return PathRange(m_path); }
658 
659 
660 	/** Iterates over the individual segments of the path.
661 
662 		Returns a forward range of `Segment2`s.
663 	*/
664 	@property auto bySegment2()
665 	const {
666 		static struct R {
667 			import std.traits : ReturnType;
668 
669 			private {
670 				string m_path;
671 				Segment2 m_front;
672 			}
673 
674 			private this(string path)
675 			{
676 				m_path = path;
677 				if (m_path.length) {
678 					auto ap = Format.getAbsolutePrefix(m_path);
679 					if (ap.length && !Format.isSeparator(ap[0]))
680 						m_front = Segment2.fromTrustedEncodedString(null, Format.defaultSeparator);
681 					else readFront();
682 				}
683 			}
684 
685 			@property bool empty() const nothrow @nogc { return m_path.length == 0 && m_front == Segment2.init; }
686 
687 			@property R save() { return this; }
688 
689 			@property Segment2 front() { return m_front; }
690 
691 			void popFront()
692 			nothrow {
693 				assert(m_front != Segment2.init);
694 				if (m_path.length) readFront();
695 				else m_front = Segment2.init;
696 			}
697 
698 			private void readFront()
699 			{
700 				auto n = Format.getFrontNode(m_path);
701 				m_path = m_path[n.length .. $];
702 
703 				char sep = '\0';
704 				if (Format.isSeparator(n[$-1])) {
705 					sep = n[$-1];
706 					n = n[0 .. $-1];
707 				}
708 				m_front = Segment2.fromTrustedEncodedString(n, sep);
709 				assert(m_front != Segment2.init);
710 			}
711 		}
712 
713 		return R(m_path);
714 	}
715 
716 	///
717 	unittest {
718 		InetPath p = "foo/bar/baz";
719 		assert(p.bySegment2.equal([
720 			InetPath.Segment2("foo", '/'),
721 			InetPath.Segment2("bar", '/'),
722 			InetPath.Segment2("baz")
723 		]));
724 	}
725 
726 
727 	/** Iterates over the path by segment, each time returning the sub path
728 		leading to that segment.
729 	*/
730 	@property auto byPrefix()
731 	const nothrow @nogc {
732 		static struct R {
733 			import std.traits : ReturnType;
734 
735 			private {
736 				string m_path;
737 				string m_remainder;
738 			}
739 
740 			private this(string path)
741 			{
742 				m_path = path;
743 				m_remainder = path;
744 				if (m_path.length) {
745 					auto ap = Format.getAbsolutePrefix(m_path);
746 					if (ap.length && !Format.isSeparator(ap[0]))
747 						m_remainder = m_remainder[ap.length .. $];
748 					else popFront();
749 				}
750 			}
751 
752 			@property bool empty() const nothrow @nogc
753 			{
754 				return m_path.length == 0;
755 			}
756 
757 			@property R save() { return this; }
758 
759 			@property GenericPath front()
760 			{
761 				return GenericPath.fromTrustedString(m_path[0 .. $-m_remainder.length]);
762 			}
763 
764 			void popFront()
765 			nothrow {
766 				assert(m_remainder.length > 0 || m_path.length > 0);
767 				if (m_remainder.length) readFront();
768 				else m_path = "";
769 			}
770 
771 			private void readFront()
772 			{
773 				auto n = Format.getFrontNode(m_remainder);
774 				m_remainder = m_remainder[n.length .. $];
775 			}
776 		}
777 
778 		return R(m_path);
779 	}
780 
781 	///
782 	unittest {
783 		assert(InetPath("foo/bar/baz").byPrefix
784 			.equal([
785 				InetPath("foo/"),
786 				InetPath("foo/bar/"),
787 				InetPath("foo/bar/baz")
788 			]));
789 
790 		assert(InetPath("/foo/bar").byPrefix
791 			.equal([
792 				InetPath("/"),
793 				InetPath("/foo/"),
794 				InetPath("/foo/bar"),
795 			]));
796 	}
797 
798 
799 	/// Returns the trailing segment of the path.
800 	@property Segment head()
801 	const {
802 		import std.array : array;
803 
804 		auto n = Format.getBackNode(m_path);
805 		char sep = '\0';
806 		if (n.length > 0 && Format.isSeparator(n[$-1])) {
807 			sep = n[$-1];
808 			n = n[0 .. $-1];
809 		}
810 
811 		static if (is(typeof(Format.decodeSingleSegment(n)) == string))
812 			string ndec = Format.decodeSingleSegment(n);
813 		else
814 			string ndec = Format.decodeSingleSegment(n).array;
815 
816 		return Segment.fromTrustedString(ndec, sep);
817 	}
818 
819 	/// Returns the trailing segment of the path.
820 	@property Segment2 head2()
821 	const @nogc {
822 		auto n = Format.getBackNode(m_path);
823 		char sep = '\0';
824 		if (n.length > 0 && Format.isSeparator(n[$-1])) {
825 			sep = n[$-1];
826 			n = n[0 .. $-1];
827 		}
828 		return Segment2.fromTrustedEncodedString(n, sep);
829 	}
830 
831 	/** Determines if the `parentPath` property is valid.
832 	*/
833 	@property bool hasParentPath()
834 	const @nogc {
835 		auto b = Format.getBackNode(m_path);
836 		return b.length < m_path.length;
837 	}
838 
839 	/** Returns a prefix of this path, where the last segment has been dropped.
840 
841 		Throws:
842 			An `Exception` is thrown if this path has no parent path. Use
843 			`hasParentPath` to test this upfront.
844 	*/
845 	@property GenericPath parentPath()
846 	const @nogc {
847 		auto b = Format.getBackNode(m_path);
848 		static immutable Exception e = new Exception("Path has no parent path");
849 		if (b.length >= m_path.length) throw e;
850 		return GenericPath.fromTrustedString(m_path[0 .. $ - b.length]);
851 	}
852 
853 
854 	/** The extension part of the file name pointed to by the path.
855 
856 		If the path is not empty and its head segment has  an extension, this
857 		returns a forward range with the extension including the leading dot.
858 		Otherwise an empty range is returned.
859 
860 		See `Segment2.extension` for a full description.
861 
862 		See_also: `Segment2.extension`, `Segment2.stripExtension`
863 	*/
864 	@property auto fileExtension()
865 	const nothrow @nogc {
866 		if (this.empty) return typeof(this.head2.extension).init;
867 		return this.head2.extension;
868 	}
869 
870 
871 	/** Returns the normalized form of the path.
872 
873 		See `normalize` for a full description.
874 	*/
875 	@property GenericPath normalized()
876 	const {
877 		GenericPath ret = this;
878 		ret.normalize();
879 		return ret;
880 	}
881 
882 	unittest {
883 		assert(PosixPath("foo/../bar").normalized == PosixPath("bar"));
884 		assert(PosixPath("foo//./bar/../baz").normalized == PosixPath("foo/baz"));
885 	}
886 
887 
888 	/** Removes any redundant path segments and replaces all separators by the
889 		default one.
890 
891 		The resulting path representation is suitable for basic semantic
892 		comparison to other normalized paths.
893 
894 		Note that there are still ways for different normalized paths to
895 		represent the same file. Examples of this are the tilde shortcut to the
896 		home directory on Unix and Linux operating systems, symbolic or hard
897 		links, and possibly environment variables are examples of this.
898 
899 		Throws:
900 			Throws an `Exception` if an absolute path contains parent directory
901 			segments ("..") that lead to a path that is a parent path of the
902 			root path.
903 	*/
904 	void normalize()
905 	{
906 		import std.array : appender, join;
907 
908 		Segment2[] newnodes;
909 		bool got_non_sep = false;
910 		foreach (n; this.bySegment2) {
911 			if (n.hasSeparator) n.separator = Format.defaultSeparator;
912 			if (!got_non_sep) {
913 				if (n.encodedName == "") newnodes ~= n;
914 				else got_non_sep = true;
915 			}
916 			switch (n.encodedName) {
917 				default: newnodes ~= n; break;
918 				case "", ".": break;
919 				case "..":
920 					enforce(!this.absolute || newnodes.length > 0, "Path goes below root node.");
921 					if (newnodes.length > 0 && newnodes[$-1].encodedName != "..") newnodes = newnodes[0 .. $-1];
922 					else newnodes ~= n;
923 					break;
924 			}
925 		}
926 
927 		auto dst = appender!string;
928 		Format.toString(newnodes, dst);
929 		m_path = dst.data;
930 	}
931 
932 	///
933 	unittest {
934 		auto path = WindowsPath("C:\\test/foo/./bar///../baz");
935 		path.normalize();
936 		assert(path.toString() == "C:\\test\\foo\\baz", path.toString());
937 
938 		path = WindowsPath("foo/../../bar/");
939 		path.normalize();
940 		assert(path.toString() == "..\\bar\\");
941 	}
942 
943 	/// Returns the string representation of the path.
944 	string toString() const nothrow @nogc { return m_path; }
945 
946 	/// Computes a hash sum, enabling storage within associative arrays.
947 	size_t toHash() const nothrow @trusted
948 	{
949 		try return typeid(string).getHash(&m_path);
950 		catch (Exception e) assert(false, "getHash for string throws!?");
951 	}
952 
953 	/** Compares two path objects.
954 
955 		Note that the exact string representation of the two paths will be
956 		compared. To get a basic semantic comparison, the paths must be
957 		normalized first.
958 	*/
959 	bool opEquals(GenericPath other) const @nogc { return this.m_path == other.m_path; }
960 
961 	/** Converts the path to a different path format.
962 
963 		Throws:
964 			A `PathValidationException` will be thrown if the path is not
965 			representable in the requested path format. This can happen
966 			especially when converting Posix or Internet paths to windows paths,
967 			since Windows paths cannot contain a number of characters that the
968 			other representations can, in theory.
969 	*/
970 	P opCast(P)() const if (isInstanceOf!(.GenericPath, P)) {
971 		static if (is(P == GenericPath)) return this;
972 		else return P(this.bySegment2.map!(n => cast(P.Segment2)n));
973 	}
974 
975 	/** Concatenates two paths.
976 
977 		The right hand side must represent a relative path.
978 	*/
979 	GenericPath opBinary(string op : "~")(string subpath) const { return this ~ GenericPath(subpath); }
980 	/// ditto
981 	GenericPath opBinary(string op : "~")(Segment subpath) const { return this ~ GenericPath(subpath); }
982 	/// ditto
983 	GenericPath opBinary(string op : "~")(Segment2 subpath) const { return this ~ GenericPath(subpath); }
984 	/// ditto
985 	GenericPath opBinary(string op : "~", F)(GenericPath!F.Segment subpath) const { return this ~ cast(Segment)(subpath); }
986 	/// ditto
987 	GenericPath opBinary(string op : "~", F)(GenericPath!F.Segment2 subpath) const { return this ~ cast(Segment2)(subpath); }
988 	/// ditto
989 	GenericPath opBinary(string op : "~")(GenericPath subpath) const nothrow {
990 		assert(!subpath.absolute || m_path.length == 0, "Cannot append absolute path.");
991 		if (endsWithSlash || empty) return GenericPath.fromTrustedString(m_path ~ subpath.m_path);
992 		else return GenericPath.fromTrustedString(m_path ~ Format.defaultSeparator ~ subpath.m_path);
993 	}
994 	/// ditto
995 	GenericPath opBinary(string op : "~", F)(GenericPath!F subpath) const if (!is(F == Format)) { return this ~ cast(GenericPath)subpath; }
996 	/// ditto
997 	GenericPath opBinary(string op : "~", R)(R entries) const nothrow
998 		if (isInputRange!R && is(ElementType!R : Segment))
999 	{
1000 		return this ~ GenericPath(entries);
1001 	}
1002 	/// ditto
1003 	GenericPath opBinary(string op : "~", R)(R entries) const nothrow
1004 		if (isInputRange!R && is(ElementType!R : Segment2))
1005 	{
1006 		return this ~ GenericPath(entries);
1007 	}
1008 
1009 	/// Appends a relative path to this path.
1010 	void opOpAssign(string op : "~", T)(T op) { this = this ~ op; }
1011 
1012 	/** Tests whether the given path is a prefix of this path.
1013 
1014 		Any path separators will be ignored during the comparison.
1015 	*/
1016 	bool startsWith(GenericPath prefix)
1017 	const nothrow {
1018 		return bySegment2.map!(n => n.name).startsWith(prefix.bySegment2.map!(n => n.name));
1019 	}
1020 }
1021 
1022 unittest {
1023 	assert(PosixPath("hello/world").bySegment.equal([PosixPath.Segment("hello",'/'), PosixPath.Segment("world")]));
1024 	assert(PosixPath("/hello/world/").bySegment.equal([PosixPath.Segment("",'/'), PosixPath.Segment("hello",'/'), PosixPath.Segment("world",'/')]));
1025 	assert(PosixPath("hello\\world").bySegment.equal([PosixPath.Segment("hello\\world")]));
1026 	assert(WindowsPath("hello/world").bySegment.equal([WindowsPath.Segment("hello",'/'), WindowsPath.Segment("world")]));
1027 	assert(WindowsPath("/hello/world/").bySegment.equal([WindowsPath.Segment("",'/'), WindowsPath.Segment("hello",'/'), WindowsPath.Segment("world",'/')]));
1028 	assert(WindowsPath("hello\\w/orld").bySegment.equal([WindowsPath.Segment("hello",'\\'), WindowsPath.Segment("w",'/'), WindowsPath.Segment("orld")]));
1029 	assert(WindowsPath("hello/w\\orld").bySegment.equal([WindowsPath.Segment("hello",'/'), WindowsPath.Segment("w",'\\'), WindowsPath.Segment("orld")]));
1030 }
1031 
1032 unittest {
1033 	assert(PosixPath("hello/world").bySegment2.equal([PosixPath.Segment2("hello",'/'), PosixPath.Segment2("world")]));
1034 	assert(PosixPath("/hello/world/").bySegment2.equal([PosixPath.Segment2("",'/'), PosixPath.Segment2("hello",'/'), PosixPath.Segment2("world",'/')]));
1035 	assert(PosixPath("hello\\world").bySegment2.equal([PosixPath.Segment2("hello\\world")]));
1036 	assert(WindowsPath("hello/world").bySegment2.equal([WindowsPath.Segment2("hello",'/'), WindowsPath.Segment2("world")]));
1037 	assert(WindowsPath("/hello/world/").bySegment2.equal([WindowsPath.Segment2("",'/'), WindowsPath.Segment2("hello",'/'), WindowsPath.Segment2("world",'/')]));
1038 	assert(WindowsPath("hello\\w/orld").bySegment2.equal([WindowsPath.Segment2("hello",'\\'), WindowsPath.Segment2("w",'/'), WindowsPath.Segment2("orld")]));
1039 	assert(WindowsPath("hello/w\\orld").bySegment2.equal([WindowsPath.Segment2("hello",'/'), WindowsPath.Segment2("w",'\\'), WindowsPath.Segment2("orld")]));
1040 
1041 	assert(PosixPath("hello/world").byPrefix.equal([PosixPath("hello/"), PosixPath("hello/world")]));
1042 	assert(PosixPath("/hello/world/").byPrefix.equal([PosixPath("/"), PosixPath("/hello/"), PosixPath("/hello/world/")]));
1043 	assert(WindowsPath("C:\\Windows").byPrefix.equal([WindowsPath("C:\\"), WindowsPath("C:\\Windows")]));
1044 }
1045 
1046 unittest
1047 {
1048 	{
1049 		auto unc = "\\\\server\\share\\path";
1050 		auto uncp = WindowsPath(unc);
1051 		assert(uncp.absolute);
1052 		uncp.normalize();
1053 		version(Windows) assert(uncp.toNativeString() == unc);
1054 		assert(uncp.absolute);
1055 		assert(!uncp.endsWithSlash);
1056 	}
1057 
1058 	{
1059 		auto abspath = "/test/path/";
1060 		auto abspathp = PosixPath(abspath);
1061 		assert(abspathp.toString() == abspath);
1062 		version(Windows) {} else assert(abspathp.toNativeString() == abspath);
1063 		assert(abspathp.absolute);
1064 		assert(abspathp.endsWithSlash);
1065 		alias S = PosixPath.Segment;
1066 		assert(abspathp.bySegment.equal([S("", '/'), S("test", '/'), S("path", '/')]));
1067 	}
1068 
1069 	{
1070 		auto relpath = "test/path/";
1071 		auto relpathp = PosixPath(relpath);
1072 		assert(relpathp.toString() == relpath);
1073 		version(Windows) assert(relpathp.toNativeString() == "test/path/");
1074 		else assert(relpathp.toNativeString() == relpath);
1075 		assert(!relpathp.absolute);
1076 		assert(relpathp.endsWithSlash);
1077 		alias S = PosixPath.Segment;
1078 		assert(relpathp.bySegment.equal([S("test", '/'), S("path", '/')]));
1079 	}
1080 
1081 	{
1082 		auto winpath = "C:\\windows\\test";
1083 		auto winpathp = WindowsPath(winpath);
1084 		assert(winpathp.toString() == "C:\\windows\\test");
1085 		assert((cast(PosixPath)winpathp).toString() == "/C:/windows/test", (cast(PosixPath)winpathp).toString());
1086 		version(Windows) assert(winpathp.toNativeString() == winpath);
1087 		else assert(winpathp.toNativeString() == "/C:/windows/test");
1088 		assert(winpathp.absolute);
1089 		assert(!winpathp.endsWithSlash);
1090 		alias S = WindowsPath.Segment;
1091 		assert(winpathp.bySegment.equal([S("", '/'), S("C:", '\\'), S("windows", '\\'), S("test")]));
1092 	}
1093 
1094 	{
1095 		auto dotpath = "/test/../test2/././x/y";
1096 		auto dotpathp = PosixPath(dotpath);
1097 		assert(dotpathp.toString() == "/test/../test2/././x/y");
1098 		dotpathp.normalize();
1099 		assert(dotpathp.toString() == "/test2/x/y", dotpathp.toString());
1100 	}
1101 
1102 	{
1103 		auto dotpath = "/test/..////test2//./x/y";
1104 		auto dotpathp = PosixPath(dotpath);
1105 		assert(dotpathp.toString() == "/test/..////test2//./x/y");
1106 		dotpathp.normalize();
1107 		assert(dotpathp.toString() == "/test2/x/y");
1108 	}
1109 
1110 	assert(WindowsPath("C:\\Windows").absolute);
1111 	assert((cast(InetPath)WindowsPath("C:\\Windows")).toString() == "/C:/Windows");
1112 	assert((WindowsPath("C:\\Windows") ~ InetPath("test/this")).toString() == "C:\\Windows\\test/this");
1113 	assert(InetPath("/C:/Windows").absolute);
1114 	assert((cast(WindowsPath)InetPath("/C:/Windows")).toString() == "C:/Windows");
1115 	assert((InetPath("/C:/Windows") ~ WindowsPath("test\\this")).toString() == "/C:/Windows/test/this");
1116 	assert((InetPath("") ~ WindowsPath("foo\\bar")).toString() == "foo/bar");
1117 	assert((cast(InetPath)WindowsPath("C:\\Windows\\")).toString() == "/C:/Windows/");
1118 
1119 	assert(NativePath("").empty);
1120 
1121 	assert(PosixPath("/") ~ NativePath("foo/bar") == PosixPath("/foo/bar"));
1122 	assert(PosixPath("") ~ NativePath("foo/bar") == PosixPath("foo/bar"));
1123 	assert(PosixPath("foo") ~ NativePath("bar") == PosixPath("foo/bar"));
1124 	assert(PosixPath("foo/") ~ NativePath("bar") == PosixPath("foo/bar"));
1125 }
1126 
1127 unittest
1128 {
1129 	{
1130 		auto unc = "\\\\server\\share\\path";
1131 		auto uncp = WindowsPath(unc);
1132 		assert(uncp.absolute);
1133 		uncp.normalize();
1134 		version(Windows) assert(uncp.toNativeString() == unc);
1135 		assert(uncp.absolute);
1136 		assert(!uncp.endsWithSlash);
1137 	}
1138 
1139 	{
1140 		auto abspath = "/test/path/";
1141 		auto abspathp = PosixPath(abspath);
1142 		assert(abspathp.toString() == abspath);
1143 		version(Windows) {} else assert(abspathp.toNativeString() == abspath);
1144 		assert(abspathp.absolute);
1145 		assert(abspathp.endsWithSlash);
1146 		alias S = PosixPath.Segment2;
1147 		assert(abspathp.bySegment2.equal([S("", '/'), S("test", '/'), S("path", '/')]));
1148 	}
1149 
1150 	{
1151 		auto relpath = "test/path/";
1152 		auto relpathp = PosixPath(relpath);
1153 		assert(relpathp.toString() == relpath);
1154 		version(Windows) assert(relpathp.toNativeString() == "test/path/");
1155 		else assert(relpathp.toNativeString() == relpath);
1156 		assert(!relpathp.absolute);
1157 		assert(relpathp.endsWithSlash);
1158 		alias S = PosixPath.Segment2;
1159 		assert(relpathp.bySegment2.equal([S("test", '/'), S("path", '/')]));
1160 	}
1161 
1162 	{
1163 		auto winpath = "C:\\windows\\test";
1164 		auto winpathp = WindowsPath(winpath);
1165 		assert(winpathp.toString() == "C:\\windows\\test");
1166 		assert((cast(PosixPath)winpathp).toString() == "/C:/windows/test", (cast(PosixPath)winpathp).toString());
1167 		version(Windows) assert(winpathp.toNativeString() == winpath);
1168 		else assert(winpathp.toNativeString() == "/C:/windows/test");
1169 		assert(winpathp.absolute);
1170 		assert(!winpathp.endsWithSlash);
1171 		alias S = WindowsPath.Segment2;
1172 		assert(winpathp.bySegment2.equal([S("", '/'), S("C:", '\\'), S("windows", '\\'), S("test")]));
1173 	}
1174 }
1175 
1176 @safe unittest {
1177 	import std.array : appender;
1178 	auto app = appender!(PosixPath[]);
1179 	void test1(PosixPath p) { app.put(p); }
1180 	void test2(PosixPath[] ps) { app.put(ps); }
1181 	//void test3(const(PosixPath) p) { app.put(p); } // DMD issue 17251
1182 	//void test4(const(PosixPath)[] ps) { app.put(ps); }
1183 }
1184 
1185 unittest {
1186 	import std.exception : assertThrown, assertNotThrown;
1187 
1188 	assertThrown!PathValidationException(WindowsPath.Segment("foo/bar"));
1189 	assertThrown!PathValidationException(PosixPath.Segment("foo/bar"));
1190 	assertNotThrown!PathValidationException(InetPath.Segment("foo/bar"));
1191 
1192 	auto p = InetPath("/foo%2fbar/");
1193 	assert(p.bySegment.equal([InetPath.Segment("",'/'), InetPath.Segment("foo/bar",'/')]));
1194 	p ~= InetPath.Segment("baz/bam");
1195 	assert(p.toString() == "/foo%2fbar/baz%2Fbam", p.toString);
1196 }
1197 
1198 unittest {
1199 	import std.exception : assertThrown, assertNotThrown;
1200 
1201 	assertThrown!PathValidationException(WindowsPath.Segment2("foo/bar"));
1202 	assertThrown!PathValidationException(PosixPath.Segment2("foo/bar"));
1203 	assertNotThrown!PathValidationException(InetPath.Segment2("foo/bar"));
1204 
1205 	auto p = InetPath("/foo%2fbar/");
1206 	import std.conv : to;
1207 	assert(p.bySegment2.equal([InetPath.Segment2("",'/'), InetPath.Segment2("foo/bar",'/')]), p.bySegment2.to!string);
1208 	p ~= InetPath.Segment2("baz/bam");
1209 	assert(p.toString() == "/foo%2fbar/baz%2Fbam", p.toString);
1210 }
1211 
1212 unittest {
1213 	assert(!PosixPath("").hasParentPath);
1214 	assert(!PosixPath("/").hasParentPath);
1215 	assert(!PosixPath("foo\\bar").hasParentPath);
1216 	assert(PosixPath("foo/bar").parentPath.toString() == "foo/");
1217 	assert(PosixPath("./foo").parentPath.toString() == "./");
1218 	assert(PosixPath("./foo").parentPath.toString() == "./");
1219 
1220 	assert(!WindowsPath("").hasParentPath);
1221 	assert(!WindowsPath("/").hasParentPath);
1222 	assert(WindowsPath("foo\\bar").parentPath.toString() == "foo\\");
1223 	assert(WindowsPath("foo/bar").parentPath.toString() == "foo/");
1224 	assert(WindowsPath("./foo").parentPath.toString() == "./");
1225 	assert(WindowsPath("./foo").parentPath.toString() == "./");
1226 
1227 	assert(!InetPath("").hasParentPath);
1228 	assert(!InetPath("/").hasParentPath);
1229 	assert(InetPath("foo/bar").parentPath.toString() == "foo/");
1230 	assert(InetPath("foo/bar%2Fbaz").parentPath.toString() == "foo/");
1231 	assert(InetPath("./foo").parentPath.toString() == "./");
1232 	assert(InetPath("./foo").parentPath.toString() == "./");
1233 }
1234 
1235 unittest {
1236 	assert(WindowsPath([WindowsPath.Segment("foo"), WindowsPath.Segment("bar")]).toString() == "foo\\bar");
1237 }
1238 
1239 unittest {
1240 	assert(WindowsPath([WindowsPath.Segment2("foo"), WindowsPath.Segment2("bar")]).toString() == "foo\\bar");
1241 }
1242 
1243 /// Thrown when an invalid string representation of a path is detected.
1244 class PathValidationException : Exception {
1245 	this(string text, string file = __FILE__, size_t line = cast(size_t)__LINE__, Throwable next = null)
1246 		pure nothrow @nogc @safe
1247 	{
1248 		super(text, file, line, next);
1249 	}
1250 }
1251 
1252 /** Implements Windows path semantics.
1253 
1254 	See_also: `WindowsPath`
1255 */
1256 struct WindowsPathFormat {
1257 	static void toString(I, O)(I segments, O dst)
1258 		if (isInputRange!I && isOutputRange!(O, char))
1259 	{
1260 		char sep(char s) { return isSeparator(s) ? s : defaultSeparator; }
1261 
1262 		if (segments.empty) return;
1263 
1264 		if (segments.front.name == "" && segments.front.separator) {
1265 			auto s = segments.front.separator;
1266 			segments.popFront();
1267 			if (segments.empty || !segments.front.name.endsWith(":"))
1268 				dst.put(sep(s));
1269 		}
1270 
1271 		char lastsep = '\0';
1272 		bool first = true;
1273 		foreach (s; segments) {
1274 			if (!first || lastsep) dst.put(sep(lastsep));
1275 			else first = false;
1276 			dst.put(s.name);
1277 			lastsep = s.separator;
1278 		}
1279 		if (lastsep) dst.put(sep(lastsep));
1280 	}
1281 
1282 	unittest {
1283 		import std.array : appender;
1284 		struct Segment { string name; char separator = 0; static Segment fromTrustedString(string str, char sep = 0) pure nothrow @nogc { return Segment(str, sep); }}
1285 		string str(Segment[] segs...) { auto ret = appender!string; toString(segs, ret); return ret.data; }
1286 
1287 		assert(str() == "");
1288 		assert(str(Segment("",'/')) == "/");
1289 		assert(str(Segment("",'/'), Segment("foo")) == "/foo");
1290 		assert(str(Segment("",'\\')) == "\\");
1291 		assert(str(Segment("foo",'/'), Segment("bar",'/')) == "foo/bar/");
1292 		assert(str(Segment("",'/'), Segment("foo",'\0')) == "/foo");
1293 		assert(str(Segment("",'\\'), Segment("foo",'\\')) == "\\foo\\");
1294 		assert(str(Segment("f oo")) == "f oo");
1295 		assert(str(Segment("",'\\'), Segment("C:")) == "C:");
1296 		assert(str(Segment("",'\\'), Segment("C:", '/')) == "C:/");
1297 		assert(str(Segment("foo",'\\'), Segment("C:")) == "foo\\C:");
1298 		assert(str(Segment("foo"), Segment("bar")) == "foo\\bar");
1299 	}
1300 
1301 @safe nothrow pure:
1302 	enum defaultSeparator = '\\';
1303 
1304 	static bool isSeparator(dchar ch)
1305 	@nogc {
1306 		return ch == '\\' || ch == '/';
1307 	}
1308 
1309 	static string getAbsolutePrefix(string path)
1310 	@nogc {
1311 		if (!path.length) return null;
1312 
1313 		if (isSeparator(path[0])) {
1314 			return path[0 .. 1];
1315 		}
1316 
1317 		foreach (i; 1 .. path.length)
1318 			if (isSeparator(path[i])) {
1319 				if (path[i-1] == ':') return path[0 .. i+1];
1320 				break;
1321 			}
1322 
1323 		return path[$-1] == ':' ? path : null;
1324 	}
1325 
1326 	unittest {
1327 		assert(getAbsolutePrefix("test") == "");
1328 		assert(getAbsolutePrefix("test/") == "");
1329 		assert(getAbsolutePrefix("/test") == "/");
1330 		assert(getAbsolutePrefix("\\test") == "\\");
1331 		assert(getAbsolutePrefix("C:\\") == "C:\\");
1332 		assert(getAbsolutePrefix("C:") == "C:");
1333 		assert(getAbsolutePrefix("C:\\test") == "C:\\");
1334 		assert(getAbsolutePrefix("C:\\test\\") == "C:\\");
1335 		assert(getAbsolutePrefix("C:/") == "C:/");
1336 		assert(getAbsolutePrefix("C:/test") == "C:/");
1337 		assert(getAbsolutePrefix("C:/test/") == "C:/");
1338 		assert(getAbsolutePrefix("\\\\server") == "\\");
1339 		assert(getAbsolutePrefix("\\\\server\\") == "\\");
1340 		assert(getAbsolutePrefix("\\\\.\\") == "\\");
1341 		assert(getAbsolutePrefix("\\\\?\\") == "\\");
1342 	}
1343 
1344 	static string getFrontNode(string path)
1345 	@nogc {
1346 		foreach (i; 0 .. path.length)
1347 			if (isSeparator(path[i]))
1348 				return path[0 .. i+1];
1349 		return path;
1350 	}
1351 
1352 	unittest {
1353 		assert(getFrontNode("") == "");
1354 		assert(getFrontNode("/bar") == "/");
1355 		assert(getFrontNode("foo/bar") == "foo/");
1356 		assert(getFrontNode("foo/") == "foo/");
1357 		assert(getFrontNode("foo") == "foo");
1358 		assert(getFrontNode("\\bar") == "\\");
1359 		assert(getFrontNode("foo\\bar") == "foo\\");
1360 		assert(getFrontNode("foo\\") == "foo\\");
1361 	}
1362 
1363 	static string getBackNode(string path)
1364 	@nogc {
1365 		if (!path.length) return path;
1366 		foreach_reverse (i; 0 .. path.length-1)
1367 			if (isSeparator(path[i]))
1368 				return path[i+1 .. $];
1369 		return path;
1370 	}
1371 
1372 	unittest {
1373 		assert(getBackNode("") == "");
1374 		assert(getBackNode("/bar") == "bar");
1375 		assert(getBackNode("foo/bar") == "bar");
1376 		assert(getBackNode("foo/") == "foo/");
1377 		assert(getBackNode("foo") == "foo");
1378 		assert(getBackNode("\\bar") == "bar");
1379 		assert(getBackNode("foo\\bar") == "bar");
1380 		assert(getBackNode("foo\\") == "foo\\");
1381 	}
1382 
1383 	deprecated("Use decodeSingleSegment instead.")
1384 	static auto decodeSegment(S)(string segment)
1385 	{
1386 		static struct R {
1387 			S[2] items;
1388 			size_t i = items.length;
1389 			this(S s) { i = 1; items[i] = s; }
1390 			this(S a, S b) { i = 0; items[0] = a; items[1] = b; }
1391 			@property ref S front() { return items[i]; }
1392 			@property bool empty() const { return i >= items.length; }
1393 			void popFront() { i++; }
1394 		}
1395 
1396 		assert(segment.length > 0, "Path segment string must not be empty.");
1397 
1398 		char sep = '\0';
1399 		if (!segment.length) return R(S.fromTrustedString(null));
1400 		if (isSeparator(segment[$-1])) {
1401 			sep = segment[$-1];
1402 			segment = segment[0 .. $-1];
1403 		}
1404 
1405 		// output an absolute marker segment for "C:\" style absolute segments
1406 		if (segment.length > 0 && segment[$-1] == ':')
1407 			return R(S.fromTrustedString("", '/'), S.fromTrustedString(segment, sep));
1408 
1409 		return R(S.fromTrustedString(segment, sep));
1410 	}
1411 
1412 	deprecated unittest {
1413 		struct Segment { string name; char separator = 0; static Segment fromTrustedString(string str, char sep = 0) pure nothrow @nogc { return Segment(str, sep); }}
1414 		assert(decodeSegment!Segment("foo").equal([Segment("foo")]));
1415 		assert(decodeSegment!Segment("foo/").equal([Segment("foo", '/')]));
1416 		assert(decodeSegment!Segment("fo%20o\\").equal([Segment("fo%20o", '\\')]));
1417 		assert(decodeSegment!Segment("C:\\").equal([Segment("",'/'), Segment("C:", '\\')]));
1418 		assert(decodeSegment!Segment("bar:\\").equal([Segment("",'/'), Segment("bar:", '\\')]));
1419 	}
1420 
1421 	static string decodeSingleSegment(string segment)
1422 	@nogc {
1423 		assert(segment.length == 0 || segment[$-1] != '/');
1424 		return segment;
1425 	}
1426 
1427 	unittest {
1428 		struct Segment { string name; char separator = 0; static Segment fromTrustedString(string str, char sep = 0) pure nothrow @nogc { return Segment(str, sep); }}
1429 		assert(decodeSingleSegment("foo") == "foo");
1430 		assert(decodeSingleSegment("fo%20o") == "fo%20o");
1431 		assert(decodeSingleSegment("C:") == "C:");
1432 		assert(decodeSingleSegment("bar:") == "bar:");
1433 	}
1434 
1435 	static string validatePath(string path)
1436 	@nogc {
1437 		import std.algorithm.comparison : among;
1438 
1439 		// skip UNC prefix
1440 		if (path.startsWith("\\\\")) {
1441 			path = path[2 .. $];
1442 			while (path.length && !isSeparator(path[0])) {
1443 				if (path[0] < 32 || path[0].among('<', '>', '|'))
1444 					return "Invalid character in UNC host name.";
1445 				path = path[1 .. $];
1446 			}
1447 			if (path.length) path = path[1 .. $];
1448 		}
1449 
1450 		// stricter validation for the rest
1451 		bool had_sep = false;
1452 		foreach (i, char c; path) {
1453 			if (c < 32 || c.among!('<', '>', '|', '?'))
1454 				return "Invalid character in path.";
1455 			if (isSeparator(c)) had_sep = true;
1456 			else if (c == ':' && (had_sep || i+1 < path.length && !isSeparator(path[i+1])))
1457 				return "Colon in path that is not part of a drive name.";
1458 
1459 		}
1460 		return null;
1461 	}
1462 
1463 	static string validateDecodedSegment(string segment)
1464 	@nogc {
1465 		auto pe = validatePath(segment);
1466 		if (pe) return pe;
1467 		foreach (char c; segment)
1468 			if (isSeparator(c))
1469 				return "Path segment contains separator character.";
1470 		return null;
1471 	}
1472 
1473 	unittest {
1474 		assert(validatePath("c:\\foo") is null);
1475 		assert(validatePath("\\\\?\\c:\\foo") is null);
1476 		assert(validatePath("//?\\c:\\foo") !is null);
1477 		assert(validatePath("-foo/bar\\*\\baz") is null);
1478 		assert(validatePath("foo\0bar") !is null);
1479 		assert(validatePath("foo\tbar") !is null);
1480 		assert(validatePath("\\c:\\foo") !is null);
1481 		assert(validatePath("c:d\\foo") !is null);
1482 		assert(validatePath("foo\\b:ar") !is null);
1483 		assert(validatePath("foo\\bar:\\baz") !is null);
1484 	}
1485 
1486 	static string encodeSegment(string segment)
1487 	{
1488 		assert(segment.length == 0 || segment[$-1] != '/');
1489 		return segment;
1490 	}
1491 }
1492 
1493 
1494 /** Implements Unix/Linux path semantics.
1495 
1496 	See_also: `WindowsPath`
1497 */
1498 struct PosixPathFormat {
1499 	static void toString(I, O)(I segments, O dst)
1500 	{
1501 		char lastsep = '\0';
1502 		bool first = true;
1503 		foreach (s; segments) {
1504 			if (!first || lastsep) dst.put('/');
1505 			else first = false;
1506 			dst.put(s.name);
1507 			lastsep = s.separator;
1508 		}
1509 		if (lastsep) dst.put('/');
1510 	}
1511 
1512 	unittest {
1513 		import std.array : appender;
1514 		struct Segment { string name; char separator = 0; static Segment fromTrustedString(string str, char sep = 0) pure nothrow @nogc { return Segment(str, sep); }}
1515 		string str(Segment[] segs...) { auto ret = appender!string; toString(segs, ret); return ret.data; }
1516 
1517 		assert(str() == "");
1518 		assert(str(Segment("",'/')) == "/");
1519 		assert(str(Segment("foo",'/'), Segment("bar",'/')) == "foo/bar/");
1520 		assert(str(Segment("",'/'), Segment("foo",'\0')) == "/foo");
1521 		assert(str(Segment("",'\\'), Segment("foo",'\\')) == "/foo/");
1522 		assert(str(Segment("f oo")) == "f oo");
1523 		assert(str(Segment("foo"), Segment("bar")) == "foo/bar");
1524 	}
1525 
1526 @safe nothrow pure:
1527 	enum defaultSeparator = '/';
1528 
1529 	static bool isSeparator(dchar ch)
1530 	@nogc {
1531 		return ch == '/';
1532 	}
1533 
1534 	static string getAbsolutePrefix(string path)
1535 	@nogc {
1536 		if (path.length > 0 && path[0] == '/')
1537 			return path[0 .. 1];
1538 		return null;
1539 	}
1540 
1541 	unittest {
1542 		assert(getAbsolutePrefix("/") == "/");
1543 		assert(getAbsolutePrefix("/test") == "/");
1544 		assert(getAbsolutePrefix("/test/") == "/");
1545 		assert(getAbsolutePrefix("test/") == "");
1546 		assert(getAbsolutePrefix("") == "");
1547 		assert(getAbsolutePrefix("./") == "");
1548 	}
1549 
1550 	static string getFrontNode(string path)
1551 	@nogc {
1552 		import std.string : indexOf;
1553 		auto idx = path.indexOf('/');
1554 		return idx < 0 ? path : path[0 .. idx+1];
1555 	}
1556 
1557 	unittest {
1558 		assert(getFrontNode("") == "");
1559 		assert(getFrontNode("/bar") == "/");
1560 		assert(getFrontNode("foo/bar") == "foo/");
1561 		assert(getFrontNode("foo/") == "foo/");
1562 		assert(getFrontNode("foo") == "foo");
1563 	}
1564 
1565 	static string getBackNode(string path)
1566 	@nogc {
1567 		if (!path.length) return path;
1568 		foreach_reverse (i; 0 .. path.length-1)
1569 			if (path[i] == '/')
1570 				return path[i+1 .. $];
1571 		return path;
1572 	}
1573 
1574 	unittest {
1575 		assert(getBackNode("") == "");
1576 		assert(getBackNode("/bar") == "bar");
1577 		assert(getBackNode("foo/bar") == "bar");
1578 		assert(getBackNode("foo/") == "foo/");
1579 		assert(getBackNode("foo") == "foo");
1580 	}
1581 
1582 	static string validatePath(string path)
1583 	@nogc {
1584 		foreach (char c; path)
1585 			if (c == '\0')
1586 				return "Invalid NUL character in file name";
1587 		return null;
1588 	}
1589 
1590 	static string validateDecodedSegment(string segment)
1591 	@nogc {
1592 		auto pe = validatePath(segment);
1593 		if (pe) return pe;
1594 		foreach (char c; segment)
1595 			if (isSeparator(c))
1596 				return "Path segment contains separator character.";
1597 		return null;
1598 	}
1599 
1600 	unittest {
1601 		assert(validatePath("-foo/bar*/baz?") is null);
1602 		assert(validatePath("foo\0bar") !is null);
1603 	}
1604 
1605 	deprecated("Use decodeSingleSegment instead.")
1606 	static auto decodeSegment(S)(string segment)
1607 	{
1608 		assert(segment.length > 0, "Path segment string must not be empty.");
1609 		import std.range : only;
1610 		if (!segment.length) return only(S.fromTrustedString(null, '/'));
1611 		if (segment[$-1] == '/')
1612 			return only(S.fromTrustedString(segment[0 .. $-1], '/'));
1613 		return only(S.fromTrustedString(segment));
1614 	}
1615 
1616 	deprecated unittest {
1617 		struct Segment { string name; char separator = 0; static Segment fromTrustedString(string str, char sep = 0) pure nothrow @nogc { return Segment(str, sep); }}
1618 		assert(decodeSegment!Segment("foo").equal([Segment("foo")]));
1619 		assert(decodeSegment!Segment("foo/").equal([Segment("foo", '/')]));
1620 		assert(decodeSegment!Segment("fo%20o\\").equal([Segment("fo%20o\\")]));
1621 	}
1622 
1623 	static string decodeSingleSegment(string segment)
1624 	@nogc {
1625 		assert(segment.length == 0 || segment[$-1] != '/');
1626 		return segment;
1627 	}
1628 
1629 	unittest {
1630 		struct Segment { string name; char separator = 0; static Segment fromTrustedString(string str, char sep = 0) pure nothrow @nogc { return Segment(str, sep); }}
1631 		assert(decodeSingleSegment("foo") == "foo");
1632 		assert(decodeSingleSegment("fo%20o\\") == "fo%20o\\");
1633 	}
1634 
1635 	static string encodeSegment(string segment)
1636 	{
1637 		assert(segment.length == 0 || segment[$-1] != '/');
1638 		return segment;
1639 	}
1640 }
1641 
1642 
1643 /** Implements URI/Internet path semantics.
1644 
1645 	See_also: `WindowsPath`
1646 */
1647 struct InetPathFormat {
1648 	static void toString(I, O)(I segments, O dst)
1649 	{
1650 		char lastsep = '\0';
1651 		bool first = true;
1652 		foreach (e; segments) {
1653 			if (!first || lastsep) dst.put('/');
1654 			else first = false;
1655 			static if (is(typeof(e.encodedName)))
1656 				dst.put(e.encodedName);
1657 			else encodeSegment(dst, e.name);
1658 			lastsep = e.separator;
1659 		}
1660 		if (lastsep) dst.put('/');
1661 	}
1662 
1663 	unittest {
1664 		import std.array : appender;
1665 		struct Segment { string name; char separator = 0; static Segment fromTrustedString(string str, char sep = 0) pure nothrow @nogc { return Segment(str, sep); }}
1666 		string str(Segment[] segs...) { auto ret = appender!string; toString(segs, ret); return ret.data; }
1667 		assert(str() == "");
1668 		assert(str(Segment("",'/')) == "/");
1669 		assert(str(Segment("foo",'/'), Segment("bar",'/')) == "foo/bar/");
1670 		assert(str(Segment("",'/'), Segment("foo",'\0')) == "/foo");
1671 		assert(str(Segment("",'\\'), Segment("foo",'\\')) == "/foo/");
1672 		assert(str(Segment("f oo")) == "f%20oo");
1673 		assert(str(Segment("foo"), Segment("bar")) == "foo/bar");
1674 	}
1675 
1676 @safe pure nothrow:
1677 	enum defaultSeparator = '/';
1678 
1679 	static bool isSeparator(dchar ch)
1680 	@nogc {
1681 		return ch == '/';
1682 	}
1683 
1684 	static string getAbsolutePrefix(string path)
1685 	@nogc {
1686 		if (path.length > 0 && path[0] == '/')
1687 			return path[0 .. 1];
1688 		return null;
1689 	}
1690 
1691 	unittest {
1692 		assert(getAbsolutePrefix("/") == "/");
1693 		assert(getAbsolutePrefix("/test") == "/");
1694 		assert(getAbsolutePrefix("/test/") == "/");
1695 		assert(getAbsolutePrefix("test/") == "");
1696 		assert(getAbsolutePrefix("") == "");
1697 		assert(getAbsolutePrefix("./") == "");
1698 	}
1699 
1700 	static string getFrontNode(string path)
1701 	@nogc {
1702 		import std.string : indexOf;
1703 		auto idx = path.indexOf('/');
1704 		return idx < 0 ? path : path[0 .. idx+1];
1705 	}
1706 
1707 	unittest {
1708 		assert(getFrontNode("") == "");
1709 		assert(getFrontNode("/bar") == "/");
1710 		assert(getFrontNode("foo/bar") == "foo/");
1711 		assert(getFrontNode("foo/") == "foo/");
1712 		assert(getFrontNode("foo") == "foo");
1713 	}
1714 
1715 	static string getBackNode(string path)
1716 	@nogc {
1717 		import std.string : lastIndexOf;
1718 
1719 		if (!path.length) return path;
1720 		ptrdiff_t idx;
1721 		try idx = path[0 .. $-1].lastIndexOf('/');
1722 		catch (Exception e) assert(false, e.msg);
1723 		if (idx >= 0) return path[idx+1 .. $];
1724 		return path;
1725 	}
1726 
1727 	unittest {
1728 		assert(getBackNode("") == "");
1729 		assert(getBackNode("/bar") == "bar");
1730 		assert(getBackNode("foo/bar") == "bar");
1731 		assert(getBackNode("foo/") == "foo/");
1732 		assert(getBackNode("foo") == "foo");
1733 	}
1734 
1735 	static string validatePath(string path)
1736 	@nogc {
1737 		for (size_t i = 0; i < path.length; i++) {
1738 			if (isAsciiAlphaNum(path[i]))
1739 				continue;
1740 
1741 			switch (path[i]) {
1742 				default:
1743 					return "Invalid character in internet path.";
1744 				// unreserved
1745 				case '-', '.', '_', '~':
1746 				// subdelims
1747 				case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=':
1748             	// additional delims
1749             	case ':', '@':
1750             	// segment delimiter
1751             	case '/':
1752             		break;
1753             	case '%': // pct encoding
1754             		if (path.length < i+3)
1755             			return "Unterminated percent encoding sequence in internet path.";
1756             		foreach (j; 0 .. 2) {
1757             			switch (path[++i]) {
1758             				default: return "Invalid percent encoding sequence in internet path.";
1759             				case '0': .. case '9':
1760             				case 'a': .. case 'f':
1761             				case 'A': .. case 'F':
1762             					break;
1763             			}
1764             		}
1765             		break;
1766 			}
1767 		}
1768 		return null;
1769 	}
1770 
1771 	static string validateDecodedSegment(string seg)
1772 	@nogc {
1773 		return null;
1774 	}
1775 
1776 	unittest {
1777 		assert(validatePath("") is null);
1778 		assert(validatePath("/") is null);
1779 		assert(validatePath("/test") is null);
1780 		assert(validatePath("test") is null);
1781 		assert(validatePath("/C:/test") is null);
1782 		assert(validatePath("/test%ab") is null);
1783 		assert(validatePath("/test%ag") !is null);
1784 		assert(validatePath("/test%a") !is null);
1785 		assert(validatePath("/test%") !is null);
1786 		assert(validatePath("/test§") !is null);
1787 		assert(validatePath("föö") !is null);
1788 	}
1789 
1790 	deprecated("Use decodeSingleSegment instead.")
1791 	static auto decodeSegment(S)(string segment)
1792 	{
1793 		import std.algorithm.searching : any;
1794 		import std.array : array;
1795 		import std.exception : assumeUnique;
1796 		import std.range : only;
1797 		import std.utf : byCodeUnit;
1798 
1799 		if (!segment.length) return only(S.fromTrustedString(null));
1800 		char sep = '\0';
1801 		if (segment[$-1] == '/') {
1802 			sep = '/';
1803 			segment = segment[0 .. $-1];
1804 		}
1805 
1806 		if (!segment.byCodeUnit.any!(c => c == '%'))
1807 			return only(S(segment, sep));
1808 		string n = decodeSingleSegment(segment).array;
1809 		return only(S(n, sep));
1810 	}
1811 
1812 	deprecated unittest {
1813 		struct Segment { string name; char separator = 0; static Segment fromTrustedString(string str, char sep = 0) pure nothrow @nogc { return Segment(str, sep); }}
1814 		assert(decodeSegment!Segment("foo").equal([Segment("foo")]));
1815 		assert(decodeSegment!Segment("foo/").equal([Segment("foo", '/')]));
1816 		assert(decodeSegment!Segment("fo%20o\\").equal([Segment("fo o\\")]));
1817 		assert(decodeSegment!Segment("foo%20").equal([Segment("foo ")]));
1818 	}
1819 
1820 	static auto decodeSingleSegment(string segment)
1821 	@nogc {
1822 		import std.string : indexOf;
1823 
1824 		static int hexDigit(char ch) @safe nothrow @nogc {
1825 			assert(ch >= '0' && ch <= '9' || ch >= 'A' && ch <= 'F' || ch >= 'a' && ch <= 'f');
1826 			if (ch >= '0' && ch <= '9') return ch - '0';
1827 			else if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10;
1828 			else return ch - 'A' + 10;
1829 		}
1830 
1831 		static struct R {
1832 			@safe pure nothrow @nogc:
1833 
1834 			private {
1835 				string m_str;
1836 				size_t m_index;
1837 			}
1838 
1839 			this(string s)
1840 			{
1841 				m_str = s;
1842 			}
1843 
1844 			@property bool empty() const { return m_index >= m_str.length; }
1845 
1846 			@property R save() const { return this; }
1847 
1848 			@property char front()
1849 			const {
1850 				auto ch = m_str[m_index];
1851 				if (ch != '%') return ch;
1852 
1853 				auto a = m_str[m_index+1];
1854 				auto b = m_str[m_index+2];
1855 				return cast(char)(16 * hexDigit(a) + hexDigit(b));
1856 			}
1857 
1858 			@property void popFront()
1859 			{
1860 				assert(!empty);
1861 				if (m_str[m_index] == '%') m_index += 3;
1862 				else m_index++;
1863 			}
1864 		}
1865 
1866 		return R(segment);
1867 	}
1868 
1869 	unittest {
1870 		scope (failure) assert(false);
1871 
1872 		assert(decodeSingleSegment("foo").equal("foo"));
1873 		assert(decodeSingleSegment("fo%20o\\").equal("fo o\\"));
1874 		assert(decodeSingleSegment("foo%20").equal("foo "));
1875 	}
1876 
1877 
1878 	static string encodeSegment(string segment)
1879 	{
1880 		import std.array : appender;
1881 
1882 		foreach (i, char c; segment) {
1883 			if (isAsciiAlphaNum(c)) continue;
1884 			switch (c) {
1885 				default:
1886 					auto ret = appender!string;
1887 					ret.put(segment[0 .. i]);
1888 					encodeSegment(ret, segment[i .. $]);
1889 					return ret.data;
1890 				case '-', '.', '_', '~':
1891 				case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=':
1892         		case ':', '@':
1893 					break;
1894 			}
1895 		}
1896 
1897 		return segment;
1898 	}
1899 
1900 	unittest {
1901 		assert(encodeSegment("foo") == "foo");
1902 		assert(encodeSegment("foo bar") == "foo%20bar");
1903 	}
1904 
1905 	static void encodeSegment(R)(ref R dst, string segment)
1906 	{
1907 		static immutable char[16] digit = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
1908 
1909 		foreach (char c; segment) {
1910 			switch (c) {
1911 				default:
1912 					dst.put('%');
1913 					dst.put(digit[uint(c) / 16]);
1914 					dst.put(digit[uint(c) % 16]);
1915 					break;
1916 				case 'a': .. case 'z':
1917 				case 'A': .. case 'Z':
1918 				case '0': .. case '9':
1919 				case '-', '.', '_', '~':
1920 				case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=':
1921         		case ':', '@':
1922 					dst.put(c);
1923 					break;
1924 			}
1925 		}
1926 	}
1927 }
1928 
1929 private auto extension(R)(R filename)
1930 	if (isForwardRange!R && isSomeChar!(ElementType!R))
1931 {
1932 	if (filename.empty) return filename;
1933 
1934 	static if (isArray!R) { // avoid auto decoding
1935 		filename = filename[1 .. $]; // ignore leading dot
1936 
1937 		R candidate;
1938 		while (filename.length) {
1939 			if (filename[0] == '.')
1940 				candidate = filename;
1941 			filename = filename[1 .. $];
1942 		}
1943 		return candidate;
1944 	} else {
1945 		filename.popFront(); // ignore leading dot
1946 
1947 		R candidate;
1948 		while (!filename.empty) {
1949 			if (filename.front == '.')
1950 				candidate = filename.save;
1951 			filename.popFront();
1952 		}
1953 		return candidate;
1954 	}
1955 }
1956 
1957 @safe nothrow unittest {
1958 	assert(extension("foo") == "");
1959 	assert(extension("foo.txt") == ".txt");
1960 	assert(extension(".foo") == "");
1961 	assert(extension(".foo.txt") == ".txt");
1962 	assert(extension("foo.bar.txt") == ".txt");
1963 }
1964 
1965 unittest {
1966 	assert(extension(InetPath("foo").head2.name).equal(""));
1967 	assert(extension(InetPath("foo.txt").head2.name).equal(".txt"));
1968 	assert(extension(InetPath(".foo").head2.name).equal(""));
1969 	assert(extension(InetPath(".foo.txt").head2.name).equal(".txt"));
1970 	assert(extension(InetPath("foo.bar.txt").head2.name).equal(".txt"));
1971 }
1972 
1973 
1974 private auto stripExtension(R)(R filename)
1975 	if (isForwardRange!R && isSomeChar!(ElementType!R))
1976 {
1977 	static if (isArray!R) { // make sure to return a slice
1978 		if (!filename.length) return filename;
1979 		R r = filename;
1980 		r = r[1 .. $]; // ignore leading dot
1981 		size_t cnt = 0, rcnt = r.length;
1982 		while (r.length) {
1983 			if (r[0] == '.')
1984 				rcnt = cnt;
1985 			cnt++;
1986 			r = r[1 .. $];
1987 		}
1988 		return filename[0 .. rcnt + 1];
1989 	} else {
1990 		if (filename.empty) return filename.takeExactly(0);
1991 		R r = filename.save;
1992 		size_t cnt = 0, rcnt = size_t.max;
1993 		r.popFront(); // ignore leading dot
1994 		while (!r.empty) {
1995 			if (r.front == '.')
1996 				rcnt = cnt;
1997 			cnt++;
1998 			r.popFront();
1999 		}
2000 		if (rcnt == size_t.max) return filename.takeExactly(cnt + 1);
2001 		return filename.takeExactly(rcnt + 1);
2002 	}
2003 }
2004 
2005 @safe nothrow unittest {
2006 	assert(stripExtension("foo") == "foo");
2007 	assert(stripExtension("foo.txt") == "foo");
2008 	assert(stripExtension(".foo") == ".foo");
2009 	assert(stripExtension(".foo.txt") == ".foo");
2010 	assert(stripExtension("foo.bar.txt") == "foo.bar");
2011 }
2012 
2013 unittest { // test range based path
2014 	import std.utf : byWchar;
2015 
2016 	assert(stripExtension("foo".byWchar).equal("foo"));
2017 	assert(stripExtension("foo.txt".byWchar).equal("foo"));
2018 	assert(stripExtension(".foo".byWchar).equal(".foo"));
2019 	assert(stripExtension(".foo.txt".byWchar).equal(".foo"));
2020 	assert(stripExtension("foo.bar.txt".byWchar).equal("foo.bar"));
2021 
2022 	assert(stripExtension(InetPath("foo").head2.name).equal("foo"));
2023 	assert(stripExtension(InetPath("foo.txt").head2.name).equal("foo"));
2024 	assert(stripExtension(InetPath(".foo").head2.name).equal(".foo"));
2025 	assert(stripExtension(InetPath(".foo.txt").head2.name).equal(".foo"));
2026 	assert(stripExtension(InetPath("foo.bar.txt").head2.name).equal("foo.bar"));
2027 }
2028 
2029 private static bool isAsciiAlphaNum(char ch)
2030 @safe nothrow pure @nogc {
2031 	return (uint(ch) & 0xDF) - 0x41 < 26 || uint(ch) - '0' <= 9;
2032 }
2033 
2034 unittest {
2035 	assert(!isAsciiAlphaNum('@'));
2036 	assert(isAsciiAlphaNum('A'));
2037 	assert(isAsciiAlphaNum('Z'));
2038 	assert(!isAsciiAlphaNum('['));
2039 	assert(!isAsciiAlphaNum('`'));
2040 	assert(isAsciiAlphaNum('a'));
2041 	assert(isAsciiAlphaNum('z'));
2042 	assert(!isAsciiAlphaNum('{'));
2043 	assert(!isAsciiAlphaNum('/'));
2044 	assert(isAsciiAlphaNum('0'));
2045 	assert(isAsciiAlphaNum('9'));
2046 	assert(!isAsciiAlphaNum(':'));
2047 }
2048 
2049 unittest { // regression tests
2050 	assert(NativePath("").bySegment.empty);
2051 	assert(NativePath("").bySegment2.empty);
2052 }