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