1 // Written in the D programming language.
2
3 /** This module is used to manipulate path strings.
4
5 All functions, with the exception of $(LREF expandTilde) (and in some
6 cases $(LREF absolutePath) and $(LREF relativePath)), are pure
7 string manipulation functions; they don't depend on any state outside
8 the program, nor do they perform any actual file system actions.
9 This has the consequence that the module does not make any distinction
10 between a path that points to a directory and a path that points to a
11 file, and it does not know whether or not the object pointed to by the
12 path actually exists in the file system.
13 To differentiate between these cases, use $(REF isDir, std,file) and
14 $(REF exists, std,file).
15
16 Note that on Windows, both the backslash ($(D `\`)) and the slash ($(D `/`))
17 are in principle valid directory separators. This module treats them
18 both on equal footing, but in cases where a $(I new) separator is
19 added, a backslash will be used. Furthermore, the $(LREF buildNormalizedPath)
20 function will replace all slashes with backslashes on that platform.
21
22 In general, the functions in this module assume that the input paths
23 are well-formed. (That is, they should not contain invalid characters,
24 they should follow the file system's path format, etc.) The result
25 of calling a function on an ill-formed path is undefined. When there
26 is a chance that a path or a file name is invalid (for instance, when it
27 has been input by the user), it may sometimes be desirable to use the
28 $(LREF isValidFilename) and $(LREF isValidPath) functions to check
29 this.
30
31 Most functions do not perform any memory allocations, and if a string is
32 returned, it is usually a slice of an input string. If a function
33 allocates, this is explicitly mentioned in the documentation.
34
35 $(SCRIPT inhibitQuickIndex = 1;)
36 $(DIVC quickindex,
37 $(BOOKTABLE,
38 $(TR $(TH Category) $(TH Functions))
39 $(TR $(TD Normalization) $(TD
40 $(LREF absolutePath)
41 $(LREF asAbsolutePath)
42 $(LREF asNormalizedPath)
43 $(LREF asRelativePath)
44 $(LREF buildNormalizedPath)
45 $(LREF buildPath)
46 $(LREF chainPath)
47 $(LREF expandTilde)
48 ))
49 $(TR $(TD Partitioning) $(TD
50 $(LREF baseName)
51 $(LREF dirName)
52 $(LREF dirSeparator)
53 $(LREF driveName)
54 $(LREF pathSeparator)
55 $(LREF pathSplitter)
56 $(LREF relativePath)
57 $(LREF rootName)
58 $(LREF stripDrive)
59 ))
60 $(TR $(TD Validation) $(TD
61 $(LREF isAbsolute)
62 $(LREF isDirSeparator)
63 $(LREF isRooted)
64 $(LREF isValidFilename)
65 $(LREF isValidPath)
66 ))
67 $(TR $(TD Extension) $(TD
68 $(LREF defaultExtension)
69 $(LREF extension)
70 $(LREF setExtension)
71 $(LREF stripExtension)
72 $(LREF withDefaultExtension)
73 $(LREF withExtension)
74 ))
75 $(TR $(TD Other) $(TD
76 $(LREF filenameCharCmp)
77 $(LREF filenameCmp)
78 $(LREF globMatch)
79 $(LREF CaseSensitive)
80 ))
81 ))
82
83 Authors:
84 Lars Tandle Kyllingstad,
85 $(HTTP digitalmars.com, Walter Bright),
86 Grzegorz Adam Hankiewicz,
87 Thomas K$(UUML)hne,
88 $(HTTP erdani.org, Andrei Alexandrescu)
89 Copyright:
90 Copyright (c) 2000-2014, the authors. All rights reserved.
91 License:
92 $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0)
93 Source:
94 $(PHOBOSSRC std/path.d)
95 */
96 module std.path;
97
98
99 import std.file : getcwd;
100 static import std.meta;
101 import std.range;
102 import std.traits;
103
104 version (OSX)
105 version = Darwin;
106 else version (iOS)
107 version = Darwin;
108 else version (TVOS)
109 version = Darwin;
110 else version (WatchOS)
111 version = Darwin;
112
version(StdUnittest)113 version (StdUnittest)
114 {
115 private:
116 struct TestAliasedString
117 {
118 string get() @safe @nogc pure nothrow return scope { return _s; }
119 alias get this;
120 @disable this(this);
121 string _s;
122 }
123
124 bool testAliasedString(alias func, Args...)(scope string s, scope Args args)
125 {
126 return func(TestAliasedString(s), args) == func(s, args);
127 }
128 }
129
130 /** String used to separate directory names in a path. Under
131 POSIX this is a slash, under Windows a backslash.
132 */
133 version (Posix) enum string dirSeparator = "/";
134 else version (Windows) enum string dirSeparator = "\\";
135 else static assert(0, "unsupported platform");
136
137
138
139
140 /** Path separator string. A colon under POSIX, a semicolon
141 under Windows.
142 */
143 version (Posix) enum string pathSeparator = ":";
144 else version (Windows) enum string pathSeparator = ";";
145 else static assert(0, "unsupported platform");
146
147
148
149
150 /** Determines whether the given character is a directory separator.
151
152 On Windows, this includes both $(D `\`) and $(D `/`).
153 On POSIX, it's just $(D `/`).
154 */
isDirSeparator(dchar c)155 bool isDirSeparator(dchar c) @safe pure nothrow @nogc
156 {
157 if (c == '/') return true;
158 version (Windows) if (c == '\\') return true;
159 return false;
160 }
161
162 ///
163 @safe pure nothrow @nogc unittest
164 {
version(Windows)165 version (Windows)
166 {
167 assert( '/'.isDirSeparator);
168 assert( '\\'.isDirSeparator);
169 }
170 else
171 {
172 assert( '/'.isDirSeparator);
173 assert(!'\\'.isDirSeparator);
174 }
175 }
176
177
178 /* Determines whether the given character is a drive separator.
179
180 On Windows, this is true if c is the ':' character that separates
181 the drive letter from the rest of the path. On POSIX, this always
182 returns false.
183 */
isDriveSeparator(dchar c)184 private bool isDriveSeparator(dchar c) @safe pure nothrow @nogc
185 {
186 version (Windows) return c == ':';
187 else return false;
188 }
189
190
191 /* Combines the isDirSeparator and isDriveSeparator tests. */
version(Windows)192 version (Windows) private bool isSeparator(dchar c) @safe pure nothrow @nogc
193 {
194 return isDirSeparator(c) || isDriveSeparator(c);
195 }
196 version (Posix) private alias isSeparator = isDirSeparator;
197
198
199 /* Helper function that determines the position of the last
200 drive/directory separator in a string. Returns -1 if none
201 is found.
202 */
203 private ptrdiff_t lastSeparator(R)(R path)
204 if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
205 isNarrowString!R)
206 {
207 auto i = (cast(ptrdiff_t) path.length) - 1;
208 while (i >= 0 && !isSeparator(path[i])) --i;
209 return i;
210 }
211
212
version(Windows)213 version (Windows)
214 {
215 private bool isUNC(R)(R path)
216 if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
217 isNarrowString!R)
218 {
219 return path.length >= 3 && isDirSeparator(path[0]) && isDirSeparator(path[1])
220 && !isDirSeparator(path[2]);
221 }
222
223 private ptrdiff_t uncRootLength(R)(R path)
224 if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
225 isNarrowString!R)
226 in { assert(isUNC(path)); }
227 do
228 {
229 ptrdiff_t i = 3;
230 while (i < path.length && !isDirSeparator(path[i])) ++i;
231 if (i < path.length)
232 {
233 auto j = i;
234 do { ++j; } while (j < path.length && isDirSeparator(path[j]));
235 if (j < path.length)
236 {
237 do { ++j; } while (j < path.length && !isDirSeparator(path[j]));
238 i = j;
239 }
240 }
241 return i;
242 }
243
244 private bool hasDrive(R)(R path)
245 if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
246 isNarrowString!R)
247 {
248 return path.length >= 2 && isDriveSeparator(path[1]);
249 }
250
251 private bool isDriveRoot(R)(R path)
252 if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
253 isNarrowString!R)
254 {
255 return path.length >= 3 && isDriveSeparator(path[1])
256 && isDirSeparator(path[2]);
257 }
258 }
259
260
261 /* Helper functions that strip leading/trailing slashes and backslashes
262 from a path.
263 */
264 private auto ltrimDirSeparators(R)(R path)
265 if (isSomeFiniteCharInputRange!R || isNarrowString!R)
266 {
267 static if (isRandomAccessRange!R && hasSlicing!R || isNarrowString!R)
268 {
269 int i = 0;
270 while (i < path.length && isDirSeparator(path[i]))
271 ++i;
272 return path[i .. path.length];
273 }
274 else
275 {
276 while (!path.empty && isDirSeparator(path.front))
277 path.popFront();
278 return path;
279 }
280 }
281
282 @safe unittest
283 {
284 import std.array;
285 import std.utf : byDchar;
286
287 assert(ltrimDirSeparators("//abc//").array == "abc//");
288 assert(ltrimDirSeparators("//abc//"d).array == "abc//"d);
289 assert(ltrimDirSeparators("//abc//".byDchar).array == "abc//"d);
290 }
291
292 private auto rtrimDirSeparators(R)(R path)
293 if (isBidirectionalRange!R && isSomeChar!(ElementType!R) ||
294 isNarrowString!R)
295 {
296 static if (isRandomAccessRange!R && hasSlicing!R && hasLength!R || isNarrowString!R)
297 {
298 auto i = (cast(ptrdiff_t) path.length) - 1;
299 while (i >= 0 && isDirSeparator(path[i]))
300 --i;
301 return path[0 .. i+1];
302 }
303 else
304 {
305 while (!path.empty && isDirSeparator(path.back))
306 path.popBack();
307 return path;
308 }
309 }
310
311 @safe unittest
312 {
313 import std.array;
314 import std.utf : byDchar;
315
316 assert(rtrimDirSeparators("//abc//").array == "//abc");
317 assert(rtrimDirSeparators("//abc//"d).array == "//abc"d);
318
319 assert(rtrimDirSeparators(MockBiRange!char("//abc//")).array == "//abc");
320 }
321
322 private auto trimDirSeparators(R)(R path)
323 if (isBidirectionalRange!R && isSomeChar!(ElementType!R) ||
324 isNarrowString!R)
325 {
326 return ltrimDirSeparators(rtrimDirSeparators(path));
327 }
328
329 @safe unittest
330 {
331 import std.array;
332 import std.utf : byDchar;
333
334 assert(trimDirSeparators("//abc//").array == "abc");
335 assert(trimDirSeparators("//abc//"d).array == "abc"d);
336
337 assert(trimDirSeparators(MockBiRange!char("//abc//")).array == "abc");
338 }
339
340 /** This `enum` is used as a template argument to functions which
341 compare file names, and determines whether the comparison is
342 case sensitive or not.
343 */
344 enum CaseSensitive : bool
345 {
346 /// File names are case insensitive
347 no = false,
348
349 /// File names are case sensitive
350 yes = true,
351
352 /** The default (or most common) setting for the current platform.
353 That is, `no` on Windows and Mac OS X, and `yes` on all
354 POSIX systems except Darwin (Linux, *BSD, etc.).
355 */
356 osDefault = osDefaultCaseSensitivity
357 }
358
359 ///
360 @safe unittest
361 {
362 assert(baseName!(CaseSensitive.no)("dir/file.EXT", ".ext") == "file");
363 assert(baseName!(CaseSensitive.yes)("dir/file.EXT", ".ext") != "file");
364
365 version (Posix)
366 assert(relativePath!(CaseSensitive.no)("/FOO/bar", "/foo/baz") == "../bar");
367 else
368 assert(relativePath!(CaseSensitive.no)(`c:\FOO\bar`, `c:\foo\baz`) == `..\bar`);
369 }
370
371 version (Windows) private enum osDefaultCaseSensitivity = false;
372 else version (Darwin) private enum osDefaultCaseSensitivity = false;
373 else version (Posix) private enum osDefaultCaseSensitivity = true;
374 else static assert(0);
375
376 /**
377 Params:
378 cs = Whether or not suffix matching is case-sensitive.
379 path = A path name. It can be a string, or any random-access range of
380 characters.
381 suffix = An optional suffix to be removed from the file name.
382 Returns: The name of the file in the path name, without any leading
383 directory and with an optional suffix chopped off.
384
385 If `suffix` is specified, it will be compared to `path`
386 using `filenameCmp!cs`,
387 where `cs` is an optional template parameter determining whether
388 the comparison is case sensitive or not. See the
389 $(LREF filenameCmp) documentation for details.
390
391 Note:
392 This function $(I only) strips away the specified suffix, which
393 doesn't necessarily have to represent an extension.
394 To remove the extension from a path, regardless of what the extension
395 is, use $(LREF stripExtension).
396 To obtain the filename without leading directories and without
397 an extension, combine the functions like this:
398 ---
399 assert(baseName(stripExtension("dir/file.ext")) == "file");
400 ---
401
402 Standards:
403 This function complies with
404 $(LINK2 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/basename.html,
405 the POSIX requirements for the 'basename' shell utility)
406 (with suitable adaptations for Windows paths).
407 */
408 auto baseName(R)(return scope R path)
409 if (isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) && !isSomeString!R)
410 {
411 return _baseName(path);
412 }
413
414 /// ditto
415 auto baseName(C)(return scope C[] path)
416 if (isSomeChar!C)
417 {
418 return _baseName(path);
419 }
420
421 /// ditto
422 inout(C)[] baseName(CaseSensitive cs = CaseSensitive.osDefault, C, C1)
423 (return scope inout(C)[] path, in C1[] suffix)
424 @safe pure //TODO: nothrow (because of filenameCmp())
425 if (isSomeChar!C && isSomeChar!C1)
426 {
427 auto p = baseName(path);
428 if (p.length > suffix.length
429 && filenameCmp!cs(cast(const(C)[])p[$-suffix.length .. $], suffix) == 0)
430 {
431 return p[0 .. $-suffix.length];
432 }
433 else return p;
434 }
435
436 ///
437 @safe unittest
438 {
439 assert(baseName("dir/file.ext") == "file.ext");
440 assert(baseName("dir/file.ext", ".ext") == "file");
441 assert(baseName("dir/file.ext", ".xyz") == "file.ext");
442 assert(baseName("dir/filename", "name") == "file");
443 assert(baseName("dir/subdir/") == "subdir");
444
version(Windows)445 version (Windows)
446 {
447 assert(baseName(`d:file.ext`) == "file.ext");
448 assert(baseName(`d:\dir\file.ext`) == "file.ext");
449 }
450 }
451
452 @safe unittest
453 {
454 assert(baseName("").empty);
455 assert(baseName("file.ext"w) == "file.ext");
456 assert(baseName("file.ext"d, ".ext") == "file");
457 assert(baseName("file", "file"w.dup) == "file");
458 assert(baseName("dir/file.ext"d.dup) == "file.ext");
459 assert(baseName("dir/file.ext", ".ext"d) == "file");
460 assert(baseName("dir/file"w, "file"d) == "file");
461 assert(baseName("dir///subdir////") == "subdir");
462 assert(baseName("dir/subdir.ext/", ".ext") == "subdir");
463 assert(baseName("dir/subdir/".dup, "subdir") == "subdir");
464 assert(baseName("/"w.dup) == "/");
465 assert(baseName("//"d.dup) == "/");
466 assert(baseName("///") == "/");
467
468 assert(baseName!(CaseSensitive.yes)("file.ext", ".EXT") == "file.ext");
469 assert(baseName!(CaseSensitive.no)("file.ext", ".EXT") == "file");
470
471 {
472 auto r = MockRange!(immutable(char))(`dir/file.ext`);
473 auto s = r.baseName();
474 foreach (i, c; `file`)
475 assert(s[i] == c);
476 }
477
version(Windows)478 version (Windows)
479 {
480 assert(baseName(`dir\file.ext`) == `file.ext`);
481 assert(baseName(`dir\file.ext`, `.ext`) == `file`);
482 assert(baseName(`dir\file`, `file`) == `file`);
483 assert(baseName(`d:file.ext`) == `file.ext`);
484 assert(baseName(`d:file.ext`, `.ext`) == `file`);
485 assert(baseName(`d:file`, `file`) == `file`);
486 assert(baseName(`dir\\subdir\\\`) == `subdir`);
487 assert(baseName(`dir\subdir.ext\`, `.ext`) == `subdir`);
488 assert(baseName(`dir\subdir\`, `subdir`) == `subdir`);
489 assert(baseName(`\`) == `\`);
490 assert(baseName(`\\`) == `\`);
491 assert(baseName(`\\\`) == `\`);
492 assert(baseName(`d:\`) == `\`);
493 assert(baseName(`d:`).empty);
494 assert(baseName(`\\server\share\file`) == `file`);
495 assert(baseName(`\\server\share\`) == `\`);
496 assert(baseName(`\\server\share`) == `\`);
497
498 auto r = MockRange!(immutable(char))(`\\server\share`);
499 auto s = r.baseName();
500 foreach (i, c; `\`)
501 assert(s[i] == c);
502 }
503
504 assert(baseName(stripExtension("dir/file.ext")) == "file");
505
506 static assert(baseName("dir/file.ext") == "file.ext");
507 static assert(baseName("dir/file.ext", ".ext") == "file");
508
509 static struct DirEntry { string s; alias s this; }
510 assert(baseName(DirEntry("dir/file.ext")) == "file.ext");
511 }
512
513 @safe unittest
514 {
515 assert(testAliasedString!baseName("file"));
516
517 enum S : string { a = "file/path/to/test" }
518 assert(S.a.baseName == "test");
519
520 char[S.a.length] sa = S.a[];
521 assert(sa.baseName == "test");
522 }
523
524 private R _baseName(R)(return scope R path)
525 if (isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) || isNarrowString!R)
526 {
527 auto p1 = stripDrive(path);
528 if (p1.empty)
529 {
530 version (Windows) if (isUNC(path))
531 return path[0 .. 1];
532 static if (isSomeString!R)
533 return null;
534 else
535 return p1; // which is empty
536 }
537
538 auto p2 = rtrimDirSeparators(p1);
539 if (p2.empty) return p1[0 .. 1];
540
541 return p2[lastSeparator(p2)+1 .. p2.length];
542 }
543
544 /** Returns the parent directory of `path`. On Windows, this
545 includes the drive letter if present. If `path` is a relative path and
546 the parent directory is the current working directory, returns `"."`.
547
548 Params:
549 path = A path name.
550
551 Returns:
552 A slice of `path` or `"."`.
553
554 Standards:
555 This function complies with
556 $(LINK2 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/dirname.html,
557 the POSIX requirements for the 'dirname' shell utility)
558 (with suitable adaptations for Windows paths).
559 */
560 auto dirName(R)(return scope R path)
561 if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) && !isSomeString!R)
562 {
563 return _dirName(path);
564 }
565
566 /// ditto
567 auto dirName(C)(return scope C[] path)
568 if (isSomeChar!C)
569 {
570 return _dirName(path);
571 }
572
573 ///
574 @safe unittest
575 {
576 assert(dirName("") == ".");
577 assert(dirName("file"w) == ".");
578 assert(dirName("dir/"d) == ".");
579 assert(dirName("dir///") == ".");
580 assert(dirName("dir/file"w.dup) == "dir");
581 assert(dirName("dir///file"d.dup) == "dir");
582 assert(dirName("dir/subdir/") == "dir");
583 assert(dirName("/dir/file"w) == "/dir");
584 assert(dirName("/file"d) == "/");
585 assert(dirName("/") == "/");
586 assert(dirName("///") == "/");
587
version(Windows)588 version (Windows)
589 {
590 assert(dirName(`dir\`) == `.`);
591 assert(dirName(`dir\\\`) == `.`);
592 assert(dirName(`dir\file`) == `dir`);
593 assert(dirName(`dir\\\file`) == `dir`);
594 assert(dirName(`dir\subdir\`) == `dir`);
595 assert(dirName(`\dir\file`) == `\dir`);
596 assert(dirName(`\file`) == `\`);
597 assert(dirName(`\`) == `\`);
598 assert(dirName(`\\\`) == `\`);
599 assert(dirName(`d:`) == `d:`);
600 assert(dirName(`d:file`) == `d:`);
601 assert(dirName(`d:\`) == `d:\`);
602 assert(dirName(`d:\file`) == `d:\`);
603 assert(dirName(`d:\dir\file`) == `d:\dir`);
604 assert(dirName(`\\server\share\dir\file`) == `\\server\share\dir`);
605 assert(dirName(`\\server\share\file`) == `\\server\share`);
606 assert(dirName(`\\server\share\`) == `\\server\share`);
607 assert(dirName(`\\server\share`) == `\\server\share`);
608 }
609 }
610
611 @safe unittest
612 {
613 assert(testAliasedString!dirName("file"));
614
615 enum S : string { a = "file/path/to/test" }
616 assert(S.a.dirName == "file/path/to");
617
618 char[S.a.length] sa = S.a[];
619 assert(sa.dirName == "file/path/to");
620 }
621
622 @safe unittest
623 {
624 static assert(dirName("dir/file") == "dir");
625
626 import std.array;
627 import std.utf : byChar, byWchar, byDchar;
628
629 assert(dirName("".byChar).array == ".");
630 assert(dirName("file"w.byWchar).array == "."w);
631 assert(dirName("dir/"d.byDchar).array == "."d);
632 assert(dirName("dir///".byChar).array == ".");
633 assert(dirName("dir/subdir/".byChar).array == "dir");
634 assert(dirName("/dir/file"w.byWchar).array == "/dir"w);
635 assert(dirName("/file"d.byDchar).array == "/"d);
636 assert(dirName("/".byChar).array == "/");
637 assert(dirName("///".byChar).array == "/");
638
version(Windows)639 version (Windows)
640 {
641 assert(dirName(`dir\`.byChar).array == `.`);
642 assert(dirName(`dir\\\`.byChar).array == `.`);
643 assert(dirName(`dir\file`.byChar).array == `dir`);
644 assert(dirName(`dir\\\file`.byChar).array == `dir`);
645 assert(dirName(`dir\subdir\`.byChar).array == `dir`);
646 assert(dirName(`\dir\file`.byChar).array == `\dir`);
647 assert(dirName(`\file`.byChar).array == `\`);
648 assert(dirName(`\`.byChar).array == `\`);
649 assert(dirName(`\\\`.byChar).array == `\`);
650 assert(dirName(`d:`.byChar).array == `d:`);
651 assert(dirName(`d:file`.byChar).array == `d:`);
652 assert(dirName(`d:\`.byChar).array == `d:\`);
653 assert(dirName(`d:\file`.byChar).array == `d:\`);
654 assert(dirName(`d:\dir\file`.byChar).array == `d:\dir`);
655 assert(dirName(`\\server\share\dir\file`.byChar).array == `\\server\share\dir`);
656 assert(dirName(`\\server\share\file`) == `\\server\share`);
657 assert(dirName(`\\server\share\`.byChar).array == `\\server\share`);
658 assert(dirName(`\\server\share`.byChar).array == `\\server\share`);
659 }
660
661 //static assert(dirName("dir/file".byChar).array == "dir");
662 }
663
_dirName(R)664 private auto _dirName(R)(return scope R path)
665 {
666 static auto result(bool dot, typeof(path[0 .. 1]) p)
667 {
668 static if (isSomeString!R)
669 return dot ? "." : p;
670 else
671 {
672 import std.range : choose, only;
673 return choose(dot, only(cast(ElementEncodingType!R)'.'), p);
674 }
675 }
676
677 if (path.empty)
678 return result(true, path[0 .. 0]);
679
680 auto p = rtrimDirSeparators(path);
681 if (p.empty)
682 return result(false, path[0 .. 1]);
683
684 version (Windows)
685 {
686 if (isUNC(p) && uncRootLength(p) == p.length)
687 return result(false, p);
688
689 if (p.length == 2 && isDriveSeparator(p[1]) && path.length > 2)
690 return result(false, path[0 .. 3]);
691 }
692
693 auto i = lastSeparator(p);
694 if (i == -1)
695 return result(true, p);
696 if (i == 0)
697 return result(false, p[0 .. 1]);
698
699 version (Windows)
700 {
701 // If the directory part is either d: or d:\
702 // do not chop off the last symbol.
703 if (isDriveSeparator(p[i]) || isDriveSeparator(p[i-1]))
704 return result(false, p[0 .. i+1]);
705 }
706 // Remove any remaining trailing (back)slashes.
707 return result(false, rtrimDirSeparators(p[0 .. i]));
708 }
709
710 /** Returns the root directory of the specified path, or `null` if the
711 path is not rooted.
712
713 Params:
714 path = A path name.
715
716 Returns:
717 A slice of `path`.
718 */
719 auto rootName(R)(R path)
720 if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) && !isSomeString!R)
721 {
722 return _rootName(path);
723 }
724
725 /// ditto
726 auto rootName(C)(C[] path)
727 if (isSomeChar!C)
728 {
729 return _rootName(path);
730 }
731
732 ///
733 @safe unittest
734 {
735 assert(rootName("") is null);
736 assert(rootName("foo") is null);
737 assert(rootName("/") == "/");
738 assert(rootName("/foo/bar") == "/");
739
version(Windows)740 version (Windows)
741 {
742 assert(rootName("d:foo") is null);
743 assert(rootName(`d:\foo`) == `d:\`);
744 assert(rootName(`\\server\share\foo`) == `\\server\share`);
745 assert(rootName(`\\server\share`) == `\\server\share`);
746 }
747 }
748
749 @safe unittest
750 {
751 assert(testAliasedString!rootName("/foo/bar"));
752
753 enum S : string { a = "/foo/bar" }
754 assert(S.a.rootName == "/");
755
756 char[S.a.length] sa = S.a[];
757 assert(sa.rootName == "/");
758 }
759
760 @safe unittest
761 {
762 import std.array;
763 import std.utf : byChar;
764
765 assert(rootName("".byChar).array == "");
766 assert(rootName("foo".byChar).array == "");
767 assert(rootName("/".byChar).array == "/");
768 assert(rootName("/foo/bar".byChar).array == "/");
769
version(Windows)770 version (Windows)
771 {
772 assert(rootName("d:foo".byChar).array == "");
773 assert(rootName(`d:\foo`.byChar).array == `d:\`);
774 assert(rootName(`\\server\share\foo`.byChar).array == `\\server\share`);
775 assert(rootName(`\\server\share`.byChar).array == `\\server\share`);
776 }
777 }
778
_rootName(R)779 private auto _rootName(R)(R path)
780 {
781 if (path.empty)
782 goto Lnull;
783
784 version (Posix)
785 {
786 if (isDirSeparator(path[0])) return path[0 .. 1];
787 }
788 else version (Windows)
789 {
790 if (isDirSeparator(path[0]))
791 {
792 if (isUNC(path)) return path[0 .. uncRootLength(path)];
793 else return path[0 .. 1];
794 }
795 else if (path.length >= 3 && isDriveSeparator(path[1]) &&
796 isDirSeparator(path[2]))
797 {
798 return path[0 .. 3];
799 }
800 }
801 else static assert(0, "unsupported platform");
802
803 assert(!isRooted(path));
804 Lnull:
805 static if (is(StringTypeOf!R))
806 return null; // legacy code may rely on null return rather than slice
807 else
808 return path[0 .. 0];
809 }
810
811 /**
812 Get the drive portion of a path.
813
814 Params:
815 path = string or range of characters
816
817 Returns:
818 A slice of `path` that is the drive, or an empty range if the drive
819 is not specified. In the case of UNC paths, the network share
820 is returned.
821
822 Always returns an empty range on POSIX.
823 */
824 auto driveName(R)(R path)
825 if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) && !isSomeString!R)
826 {
827 return _driveName(path);
828 }
829
830 /// ditto
831 auto driveName(C)(C[] path)
832 if (isSomeChar!C)
833 {
834 return _driveName(path);
835 }
836
837 ///
838 @safe unittest
839 {
840 import std.range : empty;
841 version (Posix) assert(driveName("c:/foo").empty);
version(Windows)842 version (Windows)
843 {
844 assert(driveName(`dir\file`).empty);
845 assert(driveName(`d:file`) == "d:");
846 assert(driveName(`d:\file`) == "d:");
847 assert(driveName("d:") == "d:");
848 assert(driveName(`\\server\share\file`) == `\\server\share`);
849 assert(driveName(`\\server\share\`) == `\\server\share`);
850 assert(driveName(`\\server\share`) == `\\server\share`);
851
852 static assert(driveName(`d:\file`) == "d:");
853 }
854 }
855
856 @safe unittest
857 {
858 assert(testAliasedString!driveName("d:/file"));
859
860 version (Posix)
861 immutable result = "";
862 else version (Windows)
863 immutable result = "d:";
864
865 enum S : string { a = "d:/file" }
866 assert(S.a.driveName == result);
867
868 char[S.a.length] sa = S.a[];
869 assert(sa.driveName == result);
870 }
871
872 @safe unittest
873 {
874 import std.array;
875 import std.utf : byChar;
876
877 version (Posix) assert(driveName("c:/foo".byChar).empty);
version(Windows)878 version (Windows)
879 {
880 assert(driveName(`dir\file`.byChar).empty);
881 assert(driveName(`d:file`.byChar).array == "d:");
882 assert(driveName(`d:\file`.byChar).array == "d:");
883 assert(driveName("d:".byChar).array == "d:");
884 assert(driveName(`\\server\share\file`.byChar).array == `\\server\share`);
885 assert(driveName(`\\server\share\`.byChar).array == `\\server\share`);
886 assert(driveName(`\\server\share`.byChar).array == `\\server\share`);
887
888 static assert(driveName(`d:\file`).array == "d:");
889 }
890 }
891
_driveName(R)892 private auto _driveName(R)(R path)
893 {
894 version (Windows)
895 {
896 if (hasDrive(path))
897 return path[0 .. 2];
898 else if (isUNC(path))
899 return path[0 .. uncRootLength(path)];
900 }
901 static if (isSomeString!R)
902 return cast(ElementEncodingType!R[]) null; // legacy code may rely on null return rather than slice
903 else
904 return path[0 .. 0];
905 }
906
907 /** Strips the drive from a Windows path. On POSIX, the path is returned
908 unaltered.
909
910 Params:
911 path = A pathname
912
913 Returns: A slice of path without the drive component.
914 */
915 auto stripDrive(R)(R path)
916 if (isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) && !isSomeString!R)
917 {
918 return _stripDrive(path);
919 }
920
921 /// ditto
922 auto stripDrive(C)(C[] path)
923 if (isSomeChar!C)
924 {
925 return _stripDrive(path);
926 }
927
928 ///
929 @safe unittest
930 {
version(Windows)931 version (Windows)
932 {
933 assert(stripDrive(`d:\dir\file`) == `\dir\file`);
934 assert(stripDrive(`\\server\share\dir\file`) == `\dir\file`);
935 }
936 }
937
938 @safe unittest
939 {
940 assert(testAliasedString!stripDrive("d:/dir/file"));
941
942 version (Posix)
943 immutable result = "d:/dir/file";
944 else version (Windows)
945 immutable result = "/dir/file";
946
947 enum S : string { a = "d:/dir/file" }
948 assert(S.a.stripDrive == result);
949
950 char[S.a.length] sa = S.a[];
951 assert(sa.stripDrive == result);
952 }
953
954 @safe unittest
955 {
version(Windows)956 version (Windows)
957 {
958 assert(stripDrive(`d:\dir\file`) == `\dir\file`);
959 assert(stripDrive(`\\server\share\dir\file`) == `\dir\file`);
960 static assert(stripDrive(`d:\dir\file`) == `\dir\file`);
961
962 auto r = MockRange!(immutable(char))(`d:\dir\file`);
963 auto s = r.stripDrive();
964 foreach (i, c; `\dir\file`)
965 assert(s[i] == c);
966 }
version(Posix)967 version (Posix)
968 {
969 assert(stripDrive(`d:\dir\file`) == `d:\dir\file`);
970
971 auto r = MockRange!(immutable(char))(`d:\dir\file`);
972 auto s = r.stripDrive();
973 foreach (i, c; `d:\dir\file`)
974 assert(s[i] == c);
975 }
976 }
977
_stripDrive(R)978 private auto _stripDrive(R)(R path)
979 {
980 version (Windows)
981 {
982 if (hasDrive!(BaseOf!R)(path)) return path[2 .. path.length];
983 else if (isUNC!(BaseOf!R)(path)) return path[uncRootLength!(BaseOf!R)(path) .. path.length];
984 }
985 return path;
986 }
987
988
989 /* Helper function that returns the position of the filename/extension
990 separator dot in path.
991
992 Params:
993 path = file spec as string or indexable range
994 Returns:
995 index of extension separator (the dot), or -1 if not found
996 */
997 private ptrdiff_t extSeparatorPos(R)(const R path)
998 if (isRandomAccessRange!R && hasLength!R && isSomeChar!(ElementType!R) ||
999 isNarrowString!R)
1000 {
1001 for (auto i = path.length; i-- > 0 && !isSeparator(path[i]); )
1002 {
1003 if (path[i] == '.' && i > 0 && !isSeparator(path[i-1]))
1004 return i;
1005 }
1006 return -1;
1007 }
1008
1009 @safe unittest
1010 {
1011 assert(extSeparatorPos("file") == -1);
1012 assert(extSeparatorPos("file.ext"w) == 4);
1013 assert(extSeparatorPos("file.ext1.ext2"d) == 9);
1014 assert(extSeparatorPos(".foo".dup) == -1);
1015 assert(extSeparatorPos(".foo.ext"w.dup) == 4);
1016 }
1017
1018 @safe unittest
1019 {
1020 assert(extSeparatorPos("dir/file"d.dup) == -1);
1021 assert(extSeparatorPos("dir/file.ext") == 8);
1022 assert(extSeparatorPos("dir/file.ext1.ext2"w) == 13);
1023 assert(extSeparatorPos("dir/.foo"d) == -1);
1024 assert(extSeparatorPos("dir/.foo.ext".dup) == 8);
1025
version(Windows)1026 version (Windows)
1027 {
1028 assert(extSeparatorPos("dir\\file") == -1);
1029 assert(extSeparatorPos("dir\\file.ext") == 8);
1030 assert(extSeparatorPos("dir\\file.ext1.ext2") == 13);
1031 assert(extSeparatorPos("dir\\.foo") == -1);
1032 assert(extSeparatorPos("dir\\.foo.ext") == 8);
1033
1034 assert(extSeparatorPos("d:file") == -1);
1035 assert(extSeparatorPos("d:file.ext") == 6);
1036 assert(extSeparatorPos("d:file.ext1.ext2") == 11);
1037 assert(extSeparatorPos("d:.foo") == -1);
1038 assert(extSeparatorPos("d:.foo.ext") == 6);
1039 }
1040
1041 static assert(extSeparatorPos("file") == -1);
1042 static assert(extSeparatorPos("file.ext"w) == 4);
1043 }
1044
1045
1046 /**
1047 Params: path = A path name.
1048 Returns: The _extension part of a file name, including the dot.
1049
1050 If there is no _extension, `null` is returned.
1051 */
1052 auto extension(R)(R path)
1053 if (isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) ||
1054 is(StringTypeOf!R))
1055 {
1056 auto i = extSeparatorPos!(BaseOf!R)(path);
1057 if (i == -1)
1058 {
1059 static if (is(StringTypeOf!R))
1060 return StringTypeOf!R.init[]; // which is null
1061 else
1062 return path[0 .. 0];
1063 }
1064 else return path[i .. path.length];
1065 }
1066
1067 ///
1068 @safe unittest
1069 {
1070 import std.range : empty;
1071 assert(extension("file").empty);
1072 assert(extension("file.") == ".");
1073 assert(extension("file.ext"w) == ".ext");
1074 assert(extension("file.ext1.ext2"d) == ".ext2");
1075 assert(extension(".foo".dup).empty);
1076 assert(extension(".foo.ext"w.dup) == ".ext");
1077
1078 static assert(extension("file").empty);
1079 static assert(extension("file.ext") == ".ext");
1080 }
1081
1082 @safe unittest
1083 {
1084 {
1085 auto r = MockRange!(immutable(char))(`file.ext1.ext2`);
1086 auto s = r.extension();
1087 foreach (i, c; `.ext2`)
1088 assert(s[i] == c);
1089 }
1090
1091 static struct DirEntry { string s; alias s this; }
1092 assert(extension(DirEntry("file")).empty);
1093 }
1094
1095
1096 /** Remove extension from path.
1097
1098 Params:
1099 path = string or range to be sliced
1100
1101 Returns:
1102 slice of path with the extension (if any) stripped off
1103 */
1104 auto stripExtension(R)(R path)
1105 if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) && !isSomeString!R)
1106 {
1107 return _stripExtension(path);
1108 }
1109
1110 /// Ditto
1111 auto stripExtension(C)(C[] path)
1112 if (isSomeChar!C)
1113 {
1114 return _stripExtension(path);
1115 }
1116
1117 ///
1118 @safe unittest
1119 {
1120 assert(stripExtension("file") == "file");
1121 assert(stripExtension("file.ext") == "file");
1122 assert(stripExtension("file.ext1.ext2") == "file.ext1");
1123 assert(stripExtension("file.") == "file");
1124 assert(stripExtension(".file") == ".file");
1125 assert(stripExtension(".file.ext") == ".file");
1126 assert(stripExtension("dir/file.ext") == "dir/file");
1127 }
1128
1129 @safe unittest
1130 {
1131 assert(testAliasedString!stripExtension("file"));
1132
1133 enum S : string { a = "foo.bar" }
1134 assert(S.a.stripExtension == "foo");
1135
1136 char[S.a.length] sa = S.a[];
1137 assert(sa.stripExtension == "foo");
1138 }
1139
1140 @safe unittest
1141 {
1142 assert(stripExtension("file.ext"w) == "file");
1143 assert(stripExtension("file.ext1.ext2"d) == "file.ext1");
1144
1145 import std.array;
1146 import std.utf : byChar, byWchar, byDchar;
1147
1148 assert(stripExtension("file".byChar).array == "file");
1149 assert(stripExtension("file.ext"w.byWchar).array == "file");
1150 assert(stripExtension("file.ext1.ext2"d.byDchar).array == "file.ext1");
1151 }
1152
_stripExtension(R)1153 private auto _stripExtension(R)(R path)
1154 {
1155 immutable i = extSeparatorPos(path);
1156 return i == -1 ? path : path[0 .. i];
1157 }
1158
1159 /** Sets or replaces an extension.
1160
1161 If the filename already has an extension, it is replaced. If not, the
1162 extension is simply appended to the filename. Including a leading dot
1163 in `ext` is optional.
1164
1165 If the extension is empty, this function is equivalent to
1166 $(LREF stripExtension).
1167
1168 This function normally allocates a new string (the possible exception
1169 being the case when path is immutable and doesn't already have an
1170 extension).
1171
1172 Params:
1173 path = A path name
1174 ext = The new extension
1175
1176 Returns: A string containing the path given by `path`, but where
1177 the extension has been set to `ext`.
1178
1179 See_Also:
1180 $(LREF withExtension) which does not allocate and returns a lazy range.
1181 */
1182 immutable(C1)[] setExtension(C1, C2)(in C1[] path, in C2[] ext)
1183 if (isSomeChar!C1 && !is(C1 == immutable) && is(immutable C1 == immutable C2))
1184 {
1185 try
1186 {
1187 import std.conv : to;
1188 return withExtension(path, ext).to!(typeof(return));
1189 }
catch(Exception e)1190 catch (Exception e)
1191 {
1192 assert(0);
1193 }
1194 }
1195
1196 ///ditto
1197 immutable(C1)[] setExtension(C1, C2)(immutable(C1)[] path, const(C2)[] ext)
1198 if (isSomeChar!C1 && is(immutable C1 == immutable C2))
1199 {
1200 if (ext.length == 0)
1201 return stripExtension(path);
1202
1203 try
1204 {
1205 import std.conv : to;
1206 return withExtension(path, ext).to!(typeof(return));
1207 }
catch(Exception e)1208 catch (Exception e)
1209 {
1210 assert(0);
1211 }
1212 }
1213
1214 ///
1215 @safe unittest
1216 {
1217 assert(setExtension("file", "ext") == "file.ext");
1218 assert(setExtension("file"w, ".ext"w) == "file.ext");
1219 assert(setExtension("file."d, "ext"d) == "file.ext");
1220 assert(setExtension("file.", ".ext") == "file.ext");
1221 assert(setExtension("file.old"w, "new"w) == "file.new");
1222 assert(setExtension("file.old"d, ".new"d) == "file.new");
1223 }
1224
1225 @safe unittest
1226 {
1227 assert(setExtension("file"w.dup, "ext"w) == "file.ext");
1228 assert(setExtension("file"w.dup, ".ext"w) == "file.ext");
1229 assert(setExtension("file."w, "ext"w.dup) == "file.ext");
1230 assert(setExtension("file."w, ".ext"w.dup) == "file.ext");
1231 assert(setExtension("file.old"d.dup, "new"d) == "file.new");
1232 assert(setExtension("file.old"d.dup, ".new"d) == "file.new");
1233
1234 static assert(setExtension("file", "ext") == "file.ext");
1235 static assert(setExtension("file.old", "new") == "file.new");
1236
1237 static assert(setExtension("file"w.dup, "ext"w) == "file.ext");
1238 static assert(setExtension("file.old"d.dup, "new"d) == "file.new");
1239
1240 // https://issues.dlang.org/show_bug.cgi?id=10601
1241 assert(setExtension("file", "") == "file");
1242 assert(setExtension("file.ext", "") == "file");
1243 }
1244
1245 /************
1246 * Replace existing extension on filespec with new one.
1247 *
1248 * Params:
1249 * path = string or random access range representing a filespec
1250 * ext = the new extension
1251 * Returns:
1252 * Range with `path`'s extension (if any) replaced with `ext`.
1253 * The element encoding type of the returned range will be the same as `path`'s.
1254 * See_Also:
1255 * $(LREF setExtension)
1256 */
1257 auto withExtension(R, C)(R path, C[] ext)
1258 if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) &&
1259 !isSomeString!R && isSomeChar!C)
1260 {
1261 return _withExtension(path, ext);
1262 }
1263
1264 /// Ditto
1265 auto withExtension(C1, C2)(C1[] path, C2[] ext)
1266 if (isSomeChar!C1 && isSomeChar!C2)
1267 {
1268 return _withExtension(path, ext);
1269 }
1270
1271 ///
1272 @safe unittest
1273 {
1274 import std.array;
1275 assert(withExtension("file", "ext").array == "file.ext");
1276 assert(withExtension("file"w, ".ext"w).array == "file.ext");
1277 assert(withExtension("file.ext"w, ".").array == "file.");
1278
1279 import std.utf : byChar, byWchar;
1280 assert(withExtension("file".byChar, "ext").array == "file.ext");
1281 assert(withExtension("file"w.byWchar, ".ext"w).array == "file.ext"w);
1282 assert(withExtension("file.ext"w.byWchar, ".").array == "file."w);
1283 }
1284
1285 @safe unittest
1286 {
1287 import std.algorithm.comparison : equal;
1288
1289 assert(testAliasedString!withExtension("file", "ext"));
1290
1291 enum S : string { a = "foo.bar" }
1292 assert(equal(S.a.withExtension(".txt"), "foo.txt"));
1293
1294 char[S.a.length] sa = S.a[];
1295 assert(equal(sa.withExtension(".txt"), "foo.txt"));
1296 }
1297
_withExtension(R,C)1298 private auto _withExtension(R, C)(R path, C[] ext)
1299 {
1300 import std.range : only, chain;
1301 import std.utf : byUTF;
1302
1303 alias CR = Unqual!(ElementEncodingType!R);
1304 auto dot = only(CR('.'));
1305 if (ext.length == 0 || ext[0] == '.')
1306 dot.popFront(); // so dot is an empty range, too
1307 return chain(stripExtension(path).byUTF!CR, dot, ext.byUTF!CR);
1308 }
1309
1310 /** Params:
1311 path = A path name.
1312 ext = The default extension to use.
1313
1314 Returns: The path given by `path`, with the extension given by `ext`
1315 appended if the path doesn't already have one.
1316
1317 Including the dot in the extension is optional.
1318
1319 This function always allocates a new string, except in the case when
1320 path is immutable and already has an extension.
1321 */
1322 immutable(C1)[] defaultExtension(C1, C2)(in C1[] path, in C2[] ext)
1323 if (isSomeChar!C1 && is(immutable C1 == immutable C2))
1324 {
1325 import std.conv : to;
1326 return withDefaultExtension(path, ext).to!(typeof(return));
1327 }
1328
1329 ///
1330 @safe unittest
1331 {
1332 assert(defaultExtension("file", "ext") == "file.ext");
1333 assert(defaultExtension("file", ".ext") == "file.ext");
1334 assert(defaultExtension("file.", "ext") == "file.");
1335 assert(defaultExtension("file.old", "new") == "file.old");
1336 assert(defaultExtension("file.old", ".new") == "file.old");
1337 }
1338
1339 @safe unittest
1340 {
1341 assert(defaultExtension("file"w.dup, "ext"w) == "file.ext");
1342 assert(defaultExtension("file.old"d.dup, "new"d) == "file.old");
1343
1344 static assert(defaultExtension("file", "ext") == "file.ext");
1345 static assert(defaultExtension("file.old", "new") == "file.old");
1346
1347 static assert(defaultExtension("file"w.dup, "ext"w) == "file.ext");
1348 static assert(defaultExtension("file.old"d.dup, "new"d) == "file.old");
1349 }
1350
1351
1352 /********************************
1353 * Set the extension of `path` to `ext` if `path` doesn't have one.
1354 *
1355 * Params:
1356 * path = filespec as string or range
1357 * ext = extension, may have leading '.'
1358 * Returns:
1359 * range with the result
1360 */
1361 auto withDefaultExtension(R, C)(R path, C[] ext)
1362 if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) &&
1363 !isSomeString!R && isSomeChar!C)
1364 {
1365 return _withDefaultExtension(path, ext);
1366 }
1367
1368 /// Ditto
1369 auto withDefaultExtension(C1, C2)(C1[] path, C2[] ext)
1370 if (isSomeChar!C1 && isSomeChar!C2)
1371 {
1372 return _withDefaultExtension(path, ext);
1373 }
1374
1375 ///
1376 @safe unittest
1377 {
1378 import std.array;
1379 assert(withDefaultExtension("file", "ext").array == "file.ext");
1380 assert(withDefaultExtension("file"w, ".ext").array == "file.ext"w);
1381 assert(withDefaultExtension("file.", "ext").array == "file.");
1382 assert(withDefaultExtension("file", "").array == "file.");
1383
1384 import std.utf : byChar, byWchar;
1385 assert(withDefaultExtension("file".byChar, "ext").array == "file.ext");
1386 assert(withDefaultExtension("file"w.byWchar, ".ext").array == "file.ext"w);
1387 assert(withDefaultExtension("file.".byChar, "ext"d).array == "file.");
1388 assert(withDefaultExtension("file".byChar, "").array == "file.");
1389 }
1390
1391 @safe unittest
1392 {
1393 import std.algorithm.comparison : equal;
1394
1395 assert(testAliasedString!withDefaultExtension("file", "ext"));
1396
1397 enum S : string { a = "foo" }
1398 assert(equal(S.a.withDefaultExtension(".txt"), "foo.txt"));
1399
1400 char[S.a.length] sa = S.a[];
1401 assert(equal(sa.withDefaultExtension(".txt"), "foo.txt"));
1402 }
1403
_withDefaultExtension(R,C)1404 private auto _withDefaultExtension(R, C)(R path, C[] ext)
1405 {
1406 import std.range : only, chain;
1407 import std.utf : byUTF;
1408
1409 alias CR = Unqual!(ElementEncodingType!R);
1410 auto dot = only(CR('.'));
1411 immutable i = extSeparatorPos(path);
1412 if (i == -1)
1413 {
1414 if (ext.length > 0 && ext[0] == '.')
1415 ext = ext[1 .. $]; // remove any leading . from ext[]
1416 }
1417 else
1418 {
1419 // path already has an extension, so make these empty
1420 ext = ext[0 .. 0];
1421 dot.popFront();
1422 }
1423 return chain(path.byUTF!CR, dot, ext.byUTF!CR);
1424 }
1425
1426 /** Combines one or more path segments.
1427
1428 This function takes a set of path segments, given as an input
1429 range of string elements or as a set of string arguments,
1430 and concatenates them with each other. Directory separators
1431 are inserted between segments if necessary. If any of the
1432 path segments are absolute (as defined by $(LREF isAbsolute)), the
1433 preceding segments will be dropped.
1434
1435 On Windows, if one of the path segments are rooted, but not absolute
1436 (e.g. $(D `\foo`)), all preceding path segments down to the previous
1437 root will be dropped. (See below for an example.)
1438
1439 This function always allocates memory to hold the resulting path.
1440 The variadic overload is guaranteed to only perform a single
1441 allocation, as is the range version if `paths` is a forward
1442 range.
1443
1444 Params:
1445 segments = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
1446 of segments to assemble the path from.
1447 Returns: The assembled path.
1448 */
1449 immutable(ElementEncodingType!(ElementType!Range))[]
1450 buildPath(Range)(scope Range segments)
1451 if (isInputRange!Range && !isInfinite!Range && isSomeString!(ElementType!Range))
1452 {
1453 if (segments.empty) return null;
1454
1455 // If this is a forward range, we can pre-calculate a maximum length.
1456 static if (isForwardRange!Range)
1457 {
1458 auto segments2 = segments.save;
1459 size_t precalc = 0;
1460 foreach (segment; segments2) precalc += segment.length + 1;
1461 }
1462 // Otherwise, just venture a guess and resize later if necessary.
1463 else size_t precalc = 255;
1464
1465 auto buf = new Unqual!(ElementEncodingType!(ElementType!Range))[](precalc);
1466 size_t pos = 0;
foreach(segment;segments)1467 foreach (segment; segments)
1468 {
1469 if (segment.empty) continue;
1470 static if (!isForwardRange!Range)
1471 {
1472 immutable neededLength = pos + segment.length + 1;
1473 if (buf.length < neededLength)
1474 buf.length = reserve(buf, neededLength + buf.length/2);
1475 }
1476 auto r = chainPath(buf[0 .. pos], segment);
1477 size_t i;
1478 foreach (c; r)
1479 {
1480 buf[i] = c;
1481 ++i;
1482 }
1483 pos = i;
1484 }
trustedCast(U,V)1485 static U trustedCast(U, V)(V v) @trusted pure nothrow { return cast(U) v; }
1486 return trustedCast!(typeof(return))(buf[0 .. pos]);
1487 }
1488
1489 /// ditto
1490 immutable(C)[] buildPath(C)(const(C)[][] paths...)
1491 @safe pure nothrow
1492 if (isSomeChar!C)
1493 {
1494 return buildPath!(typeof(paths))(paths);
1495 }
1496
1497 ///
1498 @safe unittest
1499 {
version(Posix)1500 version (Posix)
1501 {
1502 assert(buildPath("foo", "bar", "baz") == "foo/bar/baz");
1503 assert(buildPath("/foo/", "bar/baz") == "/foo/bar/baz");
1504 assert(buildPath("/foo", "/bar") == "/bar");
1505 }
1506
version(Windows)1507 version (Windows)
1508 {
1509 assert(buildPath("foo", "bar", "baz") == `foo\bar\baz`);
1510 assert(buildPath(`c:\foo`, `bar\baz`) == `c:\foo\bar\baz`);
1511 assert(buildPath("foo", `d:\bar`) == `d:\bar`);
1512 assert(buildPath("foo", `\bar`) == `\bar`);
1513 assert(buildPath(`c:\foo`, `\bar`) == `c:\bar`);
1514 }
1515 }
1516
1517 @system unittest // non-documented
1518 {
1519 import std.range;
1520 // ir() wraps an array in a plain (i.e. non-forward) input range, so that
1521 // we can test both code paths
1522 InputRange!(C[]) ir(C)(C[][] p...) { return inputRangeObject(p.dup); }
version(Posix)1523 version (Posix)
1524 {
1525 assert(buildPath("foo") == "foo");
1526 assert(buildPath("/foo/") == "/foo/");
1527 assert(buildPath("foo", "bar") == "foo/bar");
1528 assert(buildPath("foo", "bar", "baz") == "foo/bar/baz");
1529 assert(buildPath("foo/".dup, "bar") == "foo/bar");
1530 assert(buildPath("foo///", "bar".dup) == "foo///bar");
1531 assert(buildPath("/foo"w, "bar"w) == "/foo/bar");
1532 assert(buildPath("foo"w.dup, "/bar"w) == "/bar");
1533 assert(buildPath("foo"w, "bar/"w.dup) == "foo/bar/");
1534 assert(buildPath("/"d, "foo"d) == "/foo");
1535 assert(buildPath(""d.dup, "foo"d) == "foo");
1536 assert(buildPath("foo"d, ""d.dup) == "foo");
1537 assert(buildPath("foo", "bar".dup, "baz") == "foo/bar/baz");
1538 assert(buildPath("foo"w, "/bar"w, "baz"w.dup) == "/bar/baz");
1539
1540 static assert(buildPath("foo", "bar", "baz") == "foo/bar/baz");
1541 static assert(buildPath("foo", "/bar", "baz") == "/bar/baz");
1542
1543 // The following are mostly duplicates of the above, except that the
1544 // range version does not accept mixed constness.
1545 assert(buildPath(ir("foo")) == "foo");
1546 assert(buildPath(ir("/foo/")) == "/foo/");
1547 assert(buildPath(ir("foo", "bar")) == "foo/bar");
1548 assert(buildPath(ir("foo", "bar", "baz")) == "foo/bar/baz");
1549 assert(buildPath(ir("foo/".dup, "bar".dup)) == "foo/bar");
1550 assert(buildPath(ir("foo///".dup, "bar".dup)) == "foo///bar");
1551 assert(buildPath(ir("/foo"w, "bar"w)) == "/foo/bar");
1552 assert(buildPath(ir("foo"w.dup, "/bar"w.dup)) == "/bar");
1553 assert(buildPath(ir("foo"w.dup, "bar/"w.dup)) == "foo/bar/");
1554 assert(buildPath(ir("/"d, "foo"d)) == "/foo");
1555 assert(buildPath(ir(""d.dup, "foo"d.dup)) == "foo");
1556 assert(buildPath(ir("foo"d, ""d)) == "foo");
1557 assert(buildPath(ir("foo", "bar", "baz")) == "foo/bar/baz");
1558 assert(buildPath(ir("foo"w.dup, "/bar"w.dup, "baz"w.dup)) == "/bar/baz");
1559 }
version(Windows)1560 version (Windows)
1561 {
1562 assert(buildPath("foo") == "foo");
1563 assert(buildPath(`\foo/`) == `\foo/`);
1564 assert(buildPath("foo", "bar", "baz") == `foo\bar\baz`);
1565 assert(buildPath("foo", `\bar`) == `\bar`);
1566 assert(buildPath(`c:\foo`, "bar") == `c:\foo\bar`);
1567 assert(buildPath("foo"w, `d:\bar`w.dup) == `d:\bar`);
1568 assert(buildPath(`c:\foo\bar`, `\baz`) == `c:\baz`);
1569 assert(buildPath(`\\foo\bar\baz`d, `foo`d, `\bar`d) == `\\foo\bar\bar`d);
1570
1571 static assert(buildPath("foo", "bar", "baz") == `foo\bar\baz`);
1572 static assert(buildPath("foo", `c:\bar`, "baz") == `c:\bar\baz`);
1573
1574 assert(buildPath(ir("foo")) == "foo");
1575 assert(buildPath(ir(`\foo/`)) == `\foo/`);
1576 assert(buildPath(ir("foo", "bar", "baz")) == `foo\bar\baz`);
1577 assert(buildPath(ir("foo", `\bar`)) == `\bar`);
1578 assert(buildPath(ir(`c:\foo`, "bar")) == `c:\foo\bar`);
1579 assert(buildPath(ir("foo"w.dup, `d:\bar`w.dup)) == `d:\bar`);
1580 assert(buildPath(ir(`c:\foo\bar`, `\baz`)) == `c:\baz`);
1581 assert(buildPath(ir(`\\foo\bar\baz`d, `foo`d, `\bar`d)) == `\\foo\bar\bar`d);
1582 }
1583
1584 // Test that allocation works as it should.
1585 auto manyShort = "aaa".repeat(1000).array();
1586 auto manyShortCombined = join(manyShort, dirSeparator);
1587 assert(buildPath(manyShort) == manyShortCombined);
1588 assert(buildPath(ir(manyShort)) == manyShortCombined);
1589
1590 auto fewLong = 'b'.repeat(500).array().repeat(10).array();
1591 auto fewLongCombined = join(fewLong, dirSeparator);
1592 assert(buildPath(fewLong) == fewLongCombined);
1593 assert(buildPath(ir(fewLong)) == fewLongCombined);
1594 }
1595
1596 @safe unittest
1597 {
1598 // Test for issue 7397
1599 string[] ary = ["a", "b"];
version(Posix)1600 version (Posix)
1601 {
1602 assert(buildPath(ary) == "a/b");
1603 }
version(Windows)1604 else version (Windows)
1605 {
1606 assert(buildPath(ary) == `a\b`);
1607 }
1608 }
1609
1610
1611 /**
1612 * Concatenate path segments together to form one path.
1613 *
1614 * Params:
1615 * r1 = first segment
1616 * r2 = second segment
1617 * ranges = 0 or more segments
1618 * Returns:
1619 * Lazy range which is the concatenation of r1, r2 and ranges with path separators.
1620 * The resulting element type is that of r1.
1621 * See_Also:
1622 * $(LREF buildPath)
1623 */
1624 auto chainPath(R1, R2, Ranges...)(R1 r1, R2 r2, Ranges ranges)
1625 if ((isRandomAccessRange!R1 && hasSlicing!R1 && hasLength!R1 && isSomeChar!(ElementType!R1) ||
1626 isNarrowString!R1 &&
1627 !isConvertibleToString!R1) &&
1628 (isRandomAccessRange!R2 && hasSlicing!R2 && hasLength!R2 && isSomeChar!(ElementType!R2) ||
1629 isNarrowString!R2 &&
1630 !isConvertibleToString!R2) &&
1631 (Ranges.length == 0 || is(typeof(chainPath(r2, ranges))))
1632 )
1633 {
1634 static if (Ranges.length)
1635 {
1636 return chainPath(chainPath(r1, r2), ranges);
1637 }
1638 else
1639 {
1640 import std.range : only, chain;
1641 import std.utf : byUTF;
1642
1643 alias CR = Unqual!(ElementEncodingType!R1);
1644 auto sep = only(CR(dirSeparator[0]));
1645 bool usesep = false;
1646
1647 auto pos = r1.length;
1648
1649 if (pos)
1650 {
1651 if (isRooted(r2))
1652 {
version(Posix)1653 version (Posix)
1654 {
1655 pos = 0;
1656 }
version(Windows)1657 else version (Windows)
1658 {
1659 if (isAbsolute(r2))
1660 pos = 0;
1661 else
1662 {
1663 pos = rootName(r1).length;
1664 if (pos > 0 && isDirSeparator(r1[pos - 1]))
1665 --pos;
1666 }
1667 }
1668 else
1669 static assert(0);
1670 }
1671 else if (!isDirSeparator(r1[pos - 1]))
1672 usesep = true;
1673 }
1674 if (!usesep)
1675 sep.popFront();
1676 // Return r1 ~ '/' ~ r2
1677 return chain(r1[0 .. pos].byUTF!CR, sep, r2.byUTF!CR);
1678 }
1679 }
1680
1681 ///
1682 @safe unittest
1683 {
1684 import std.array;
version(Posix)1685 version (Posix)
1686 {
1687 assert(chainPath("foo", "bar", "baz").array == "foo/bar/baz");
1688 assert(chainPath("/foo/", "bar/baz").array == "/foo/bar/baz");
1689 assert(chainPath("/foo", "/bar").array == "/bar");
1690 }
1691
version(Windows)1692 version (Windows)
1693 {
1694 assert(chainPath("foo", "bar", "baz").array == `foo\bar\baz`);
1695 assert(chainPath(`c:\foo`, `bar\baz`).array == `c:\foo\bar\baz`);
1696 assert(chainPath("foo", `d:\bar`).array == `d:\bar`);
1697 assert(chainPath("foo", `\bar`).array == `\bar`);
1698 assert(chainPath(`c:\foo`, `\bar`).array == `c:\bar`);
1699 }
1700
1701 import std.utf : byChar;
version(Posix)1702 version (Posix)
1703 {
1704 assert(chainPath("foo", "bar", "baz").array == "foo/bar/baz");
1705 assert(chainPath("/foo/".byChar, "bar/baz").array == "/foo/bar/baz");
1706 assert(chainPath("/foo", "/bar".byChar).array == "/bar");
1707 }
1708
version(Windows)1709 version (Windows)
1710 {
1711 assert(chainPath("foo", "bar", "baz").array == `foo\bar\baz`);
1712 assert(chainPath(`c:\foo`.byChar, `bar\baz`).array == `c:\foo\bar\baz`);
1713 assert(chainPath("foo", `d:\bar`).array == `d:\bar`);
1714 assert(chainPath("foo", `\bar`.byChar).array == `\bar`);
1715 assert(chainPath(`c:\foo`, `\bar`w).array == `c:\bar`);
1716 }
1717 }
1718
1719 auto chainPath(Ranges...)(auto ref Ranges ranges)
1720 if (Ranges.length >= 2 &&
1721 std.meta.anySatisfy!(isConvertibleToString, Ranges))
1722 {
1723 import std.meta : staticMap;
1724 alias Types = staticMap!(convertToString, Ranges);
1725 return chainPath!Types(ranges);
1726 }
1727
1728 @safe unittest
1729 {
1730 assert(chainPath(TestAliasedString(null), TestAliasedString(null), TestAliasedString(null)).empty);
1731 assert(chainPath(TestAliasedString(null), TestAliasedString(null), "").empty);
1732 assert(chainPath(TestAliasedString(null), "", TestAliasedString(null)).empty);
1733 static struct S { string s; }
1734 static assert(!__traits(compiles, chainPath(TestAliasedString(null), S(""), TestAliasedString(null))));
1735 }
1736
1737 /** Performs the same task as $(LREF buildPath),
1738 while at the same time resolving current/parent directory
1739 symbols (`"."` and `".."`) and removing superfluous
1740 directory separators.
1741 It will return "." if the path leads to the starting directory.
1742 On Windows, slashes are replaced with backslashes.
1743
1744 Using buildNormalizedPath on null paths will always return null.
1745
1746 Note that this function does not resolve symbolic links.
1747
1748 This function always allocates memory to hold the resulting path.
1749 Use $(LREF asNormalizedPath) to not allocate memory.
1750
1751 Params:
1752 paths = An array of paths to assemble.
1753
1754 Returns: The assembled path.
1755 */
1756 immutable(C)[] buildNormalizedPath(C)(const(C[])[] paths...)
1757 @safe pure nothrow
1758 if (isSomeChar!C)
1759 {
1760 import std.array : array;
1761 import std.exception : assumeUnique;
1762
1763 const(C)[] chained;
foreach(path;paths)1764 foreach (path; paths)
1765 {
1766 if (chained)
1767 chained = chainPath(chained, path).array;
1768 else
1769 chained = path;
1770 }
1771 auto result = asNormalizedPath(chained);
1772 // .array returns a copy, so it is unique
1773 return () @trusted { return assumeUnique(result.array); } ();
1774 }
1775
1776 ///
1777 @safe unittest
1778 {
1779 assert(buildNormalizedPath("foo", "..") == ".");
1780
version(Posix)1781 version (Posix)
1782 {
1783 assert(buildNormalizedPath("/foo/./bar/..//baz/") == "/foo/baz");
1784 assert(buildNormalizedPath("../foo/.") == "../foo");
1785 assert(buildNormalizedPath("/foo", "bar/baz/") == "/foo/bar/baz");
1786 assert(buildNormalizedPath("/foo", "/bar/..", "baz") == "/baz");
1787 assert(buildNormalizedPath("foo/./bar", "../../", "../baz") == "../baz");
1788 assert(buildNormalizedPath("/foo/./bar", "../../baz") == "/baz");
1789 }
1790
version(Windows)1791 version (Windows)
1792 {
1793 assert(buildNormalizedPath(`c:\foo\.\bar/..\\baz\`) == `c:\foo\baz`);
1794 assert(buildNormalizedPath(`..\foo\.`) == `..\foo`);
1795 assert(buildNormalizedPath(`c:\foo`, `bar\baz\`) == `c:\foo\bar\baz`);
1796 assert(buildNormalizedPath(`c:\foo`, `bar/..`) == `c:\foo`);
1797 assert(buildNormalizedPath(`\\server\share\foo`, `..\bar`) ==
1798 `\\server\share\bar`);
1799 }
1800 }
1801
1802 @safe unittest
1803 {
1804 assert(buildNormalizedPath(".", ".") == ".");
1805 assert(buildNormalizedPath("foo", "..") == ".");
1806 assert(buildNormalizedPath("", "") is null);
1807 assert(buildNormalizedPath("", ".") == ".");
1808 assert(buildNormalizedPath(".", "") == ".");
1809 assert(buildNormalizedPath(null, "foo") == "foo");
1810 assert(buildNormalizedPath("", "foo") == "foo");
1811 assert(buildNormalizedPath("", "") == "");
1812 assert(buildNormalizedPath("", null) == "");
1813 assert(buildNormalizedPath(null, "") == "");
1814 assert(buildNormalizedPath!(char)(null, null) == "");
1815
version(Posix)1816 version (Posix)
1817 {
1818 assert(buildNormalizedPath("/", "foo", "bar") == "/foo/bar");
1819 assert(buildNormalizedPath("foo", "bar", "baz") == "foo/bar/baz");
1820 assert(buildNormalizedPath("foo", "bar/baz") == "foo/bar/baz");
1821 assert(buildNormalizedPath("foo", "bar//baz///") == "foo/bar/baz");
1822 assert(buildNormalizedPath("/foo", "bar/baz") == "/foo/bar/baz");
1823 assert(buildNormalizedPath("/foo", "/bar/baz") == "/bar/baz");
1824 assert(buildNormalizedPath("/foo/..", "/bar/./baz") == "/bar/baz");
1825 assert(buildNormalizedPath("/foo/..", "bar/baz") == "/bar/baz");
1826 assert(buildNormalizedPath("/foo/../../", "bar/baz") == "/bar/baz");
1827 assert(buildNormalizedPath("/foo/bar", "../baz") == "/foo/baz");
1828 assert(buildNormalizedPath("/foo/bar", "../../baz") == "/baz");
1829 assert(buildNormalizedPath("/foo/bar", ".././/baz/..", "wee/") == "/foo/wee");
1830 assert(buildNormalizedPath("//foo/bar", "baz///wee") == "/foo/bar/baz/wee");
1831 static assert(buildNormalizedPath("/foo/..", "/bar/./baz") == "/bar/baz");
1832 }
version(Windows)1833 else version (Windows)
1834 {
1835 assert(buildNormalizedPath(`\`, `foo`, `bar`) == `\foo\bar`);
1836 assert(buildNormalizedPath(`foo`, `bar`, `baz`) == `foo\bar\baz`);
1837 assert(buildNormalizedPath(`foo`, `bar\baz`) == `foo\bar\baz`);
1838 assert(buildNormalizedPath(`foo`, `bar\\baz\\\`) == `foo\bar\baz`);
1839 assert(buildNormalizedPath(`\foo`, `bar\baz`) == `\foo\bar\baz`);
1840 assert(buildNormalizedPath(`\foo`, `\bar\baz`) == `\bar\baz`);
1841 assert(buildNormalizedPath(`\foo\..`, `\bar\.\baz`) == `\bar\baz`);
1842 assert(buildNormalizedPath(`\foo\..`, `bar\baz`) == `\bar\baz`);
1843 assert(buildNormalizedPath(`\foo\..\..\`, `bar\baz`) == `\bar\baz`);
1844 assert(buildNormalizedPath(`\foo\bar`, `..\baz`) == `\foo\baz`);
1845 assert(buildNormalizedPath(`\foo\bar`, `../../baz`) == `\baz`);
1846 assert(buildNormalizedPath(`\foo\bar`, `..\.\/baz\..`, `wee\`) == `\foo\wee`);
1847
1848 assert(buildNormalizedPath(`c:\`, `foo`, `bar`) == `c:\foo\bar`);
1849 assert(buildNormalizedPath(`c:foo`, `bar`, `baz`) == `c:foo\bar\baz`);
1850 assert(buildNormalizedPath(`c:foo`, `bar\baz`) == `c:foo\bar\baz`);
1851 assert(buildNormalizedPath(`c:foo`, `bar\\baz\\\`) == `c:foo\bar\baz`);
1852 assert(buildNormalizedPath(`c:\foo`, `bar\baz`) == `c:\foo\bar\baz`);
1853 assert(buildNormalizedPath(`c:\foo`, `\bar\baz`) == `c:\bar\baz`);
1854 assert(buildNormalizedPath(`c:\foo\..`, `\bar\.\baz`) == `c:\bar\baz`);
1855 assert(buildNormalizedPath(`c:\foo\..`, `bar\baz`) == `c:\bar\baz`);
1856 assert(buildNormalizedPath(`c:\foo\..\..\`, `bar\baz`) == `c:\bar\baz`);
1857 assert(buildNormalizedPath(`c:\foo\bar`, `..\baz`) == `c:\foo\baz`);
1858 assert(buildNormalizedPath(`c:\foo\bar`, `..\..\baz`) == `c:\baz`);
1859 assert(buildNormalizedPath(`c:\foo\bar`, `..\.\\baz\..`, `wee\`) == `c:\foo\wee`);
1860
1861 assert(buildNormalizedPath(`\\server\share`, `foo`, `bar`) == `\\server\share\foo\bar`);
1862 assert(buildNormalizedPath(`\\server\share\`, `foo`, `bar`) == `\\server\share\foo\bar`);
1863 assert(buildNormalizedPath(`\\server\share\foo`, `bar\baz`) == `\\server\share\foo\bar\baz`);
1864 assert(buildNormalizedPath(`\\server\share\foo`, `\bar\baz`) == `\\server\share\bar\baz`);
1865 assert(buildNormalizedPath(`\\server\share\foo\..`, `\bar\.\baz`) == `\\server\share\bar\baz`);
1866 assert(buildNormalizedPath(`\\server\share\foo\..`, `bar\baz`) == `\\server\share\bar\baz`);
1867 assert(buildNormalizedPath(`\\server\share\foo\..\..\`, `bar\baz`) == `\\server\share\bar\baz`);
1868 assert(buildNormalizedPath(`\\server\share\foo\bar`, `..\baz`) == `\\server\share\foo\baz`);
1869 assert(buildNormalizedPath(`\\server\share\foo\bar`, `..\..\baz`) == `\\server\share\baz`);
1870 assert(buildNormalizedPath(`\\server\share\foo\bar`, `..\.\\baz\..`, `wee\`) == `\\server\share\foo\wee`);
1871
1872 static assert(buildNormalizedPath(`\foo\..\..\`, `bar\baz`) == `\bar\baz`);
1873 }
1874 else static assert(0);
1875 }
1876
1877 @safe unittest
1878 {
1879 // Test for issue 7397
1880 string[] ary = ["a", "b"];
version(Posix)1881 version (Posix)
1882 {
1883 assert(buildNormalizedPath(ary) == "a/b");
1884 }
version(Windows)1885 else version (Windows)
1886 {
1887 assert(buildNormalizedPath(ary) == `a\b`);
1888 }
1889 }
1890
1891
1892 /** Normalize a path by resolving current/parent directory
1893 symbols (`"."` and `".."`) and removing superfluous
1894 directory separators.
1895 It will return "." if the path leads to the starting directory.
1896 On Windows, slashes are replaced with backslashes.
1897
1898 Using asNormalizedPath on empty paths will always return an empty path.
1899
1900 Does not resolve symbolic links.
1901
1902 This function always allocates memory to hold the resulting path.
1903 Use $(LREF buildNormalizedPath) to allocate memory and return a string.
1904
1905 Params:
1906 path = string or random access range representing the path to normalize
1907
1908 Returns:
1909 normalized path as a forward range
1910 */
1911
1912 auto asNormalizedPath(R)(return scope R path)
1913 if (isSomeChar!(ElementEncodingType!R) &&
1914 (isRandomAccessRange!R && hasSlicing!R && hasLength!R || isNarrowString!R) &&
1915 !isConvertibleToString!R)
1916 {
1917 alias C = Unqual!(ElementEncodingType!R);
1918 alias S = typeof(path[0 .. 0]);
1919
1920 static struct Result
1921 {
emptyResult1922 @property bool empty()
1923 {
1924 return c == c.init;
1925 }
1926
frontResult1927 @property C front()
1928 {
1929 return c;
1930 }
1931
popFrontResult1932 void popFront()
1933 {
1934 C lastc = c;
1935 c = c.init;
1936 if (!element.empty)
1937 {
1938 c = getElement0();
1939 return;
1940 }
1941 L1:
1942 while (1)
1943 {
1944 if (elements.empty)
1945 {
1946 element = element[0 .. 0];
1947 return;
1948 }
1949 element = elements.front;
1950 elements.popFront();
1951 if (isDot(element) || (rooted && isDotDot(element)))
1952 continue;
1953
1954 if (rooted || !isDotDot(element))
1955 {
1956 int n = 1;
1957 auto elements2 = elements.save;
1958 while (!elements2.empty)
1959 {
1960 auto e = elements2.front;
1961 elements2.popFront();
1962 if (isDot(e))
1963 continue;
1964 if (isDotDot(e))
1965 {
1966 --n;
1967 if (n == 0)
1968 {
1969 elements = elements2;
1970 element = element[0 .. 0];
1971 continue L1;
1972 }
1973 }
1974 else
1975 ++n;
1976 }
1977 }
1978 break;
1979 }
1980
1981 static assert(dirSeparator.length == 1);
1982 if (lastc == dirSeparator[0] || lastc == lastc.init)
1983 c = getElement0();
1984 else
1985 c = dirSeparator[0];
1986 }
1987
1988 static if (isForwardRange!R)
1989 {
saveResult1990 @property auto save()
1991 {
1992 auto result = this;
1993 result.element = element.save;
1994 result.elements = elements.save;
1995 return result;
1996 }
1997 }
1998
1999 private:
thisResult2000 this(R path)
2001 {
2002 element = rootName(path);
2003 auto i = element.length;
2004 while (i < path.length && isDirSeparator(path[i]))
2005 ++i;
2006 rooted = i > 0;
2007 elements = pathSplitter(path[i .. $]);
2008 popFront();
2009 if (c == c.init && path.length)
2010 c = C('.');
2011 }
2012
getElement0Result2013 C getElement0()
2014 {
2015 static if (isNarrowString!S) // avoid autodecode
2016 {
2017 C c = element[0];
2018 element = element[1 .. $];
2019 }
2020 else
2021 {
2022 C c = element.front;
2023 element.popFront();
2024 }
2025 version (Windows)
2026 {
2027 if (c == '/') // can appear in root element
2028 c = '\\'; // use native Windows directory separator
2029 }
2030 return c;
2031 }
2032
2033 // See if elem is "."
isDotResult2034 static bool isDot(S elem)
2035 {
2036 return elem.length == 1 && elem[0] == '.';
2037 }
2038
2039 // See if elem is ".."
isDotDotResult2040 static bool isDotDot(S elem)
2041 {
2042 return elem.length == 2 && elem[0] == '.' && elem[1] == '.';
2043 }
2044
2045 bool rooted; // the path starts with a root directory
2046 C c;
2047 S element;
2048 typeof(pathSplitter(path[0 .. 0])) elements;
2049 }
2050
2051 return Result(path);
2052 }
2053
2054 ///
2055 @safe unittest
2056 {
2057 import std.array;
2058 assert(asNormalizedPath("foo/..").array == ".");
2059
version(Posix)2060 version (Posix)
2061 {
2062 assert(asNormalizedPath("/foo/./bar/..//baz/").array == "/foo/baz");
2063 assert(asNormalizedPath("../foo/.").array == "../foo");
2064 assert(asNormalizedPath("/foo/bar/baz/").array == "/foo/bar/baz");
2065 assert(asNormalizedPath("/foo/./bar/../../baz").array == "/baz");
2066 }
2067
version(Windows)2068 version (Windows)
2069 {
2070 assert(asNormalizedPath(`c:\foo\.\bar/..\\baz\`).array == `c:\foo\baz`);
2071 assert(asNormalizedPath(`..\foo\.`).array == `..\foo`);
2072 assert(asNormalizedPath(`c:\foo\bar\baz\`).array == `c:\foo\bar\baz`);
2073 assert(asNormalizedPath(`c:\foo\bar/..`).array == `c:\foo`);
2074 assert(asNormalizedPath(`\\server\share\foo\..\bar`).array ==
2075 `\\server\share\bar`);
2076 }
2077 }
2078
2079 auto asNormalizedPath(R)(return scope auto ref R path)
2080 if (isConvertibleToString!R)
2081 {
2082 return asNormalizedPath!(StringTypeOf!R)(path);
2083 }
2084
2085 @safe unittest
2086 {
2087 assert(testAliasedString!asNormalizedPath(null));
2088 }
2089
2090 @safe unittest
2091 {
2092 import std.array;
2093 import std.utf : byChar;
2094
2095 assert(asNormalizedPath("").array is null);
2096 assert(asNormalizedPath("foo").array == "foo");
2097 assert(asNormalizedPath(".").array == ".");
2098 assert(asNormalizedPath("./.").array == ".");
2099 assert(asNormalizedPath("foo/..").array == ".");
2100
2101 auto save = asNormalizedPath("fob").save;
2102 save.popFront();
2103 assert(save.front == 'o');
2104
version(Posix)2105 version (Posix)
2106 {
2107 assert(asNormalizedPath("/foo/bar").array == "/foo/bar");
2108 assert(asNormalizedPath("foo/bar/baz").array == "foo/bar/baz");
2109 assert(asNormalizedPath("foo/bar/baz").array == "foo/bar/baz");
2110 assert(asNormalizedPath("foo/bar//baz///").array == "foo/bar/baz");
2111 assert(asNormalizedPath("/foo/bar/baz").array == "/foo/bar/baz");
2112 assert(asNormalizedPath("/foo/../bar/baz").array == "/bar/baz");
2113 assert(asNormalizedPath("/foo/../..//bar/baz").array == "/bar/baz");
2114 assert(asNormalizedPath("/foo/bar/../baz").array == "/foo/baz");
2115 assert(asNormalizedPath("/foo/bar/../../baz").array == "/baz");
2116 assert(asNormalizedPath("/foo/bar/.././/baz/../wee/").array == "/foo/wee");
2117 assert(asNormalizedPath("//foo/bar/baz///wee").array == "/foo/bar/baz/wee");
2118
2119 assert(asNormalizedPath("foo//bar").array == "foo/bar");
2120 assert(asNormalizedPath("foo/bar").array == "foo/bar");
2121
2122 //Curent dir path
2123 assert(asNormalizedPath("./").array == ".");
2124 assert(asNormalizedPath("././").array == ".");
2125 assert(asNormalizedPath("./foo/..").array == ".");
2126 assert(asNormalizedPath("foo/..").array == ".");
2127 }
version(Windows)2128 else version (Windows)
2129 {
2130 assert(asNormalizedPath(`\foo\bar`).array == `\foo\bar`);
2131 assert(asNormalizedPath(`foo\bar\baz`).array == `foo\bar\baz`);
2132 assert(asNormalizedPath(`foo\bar\baz`).array == `foo\bar\baz`);
2133 assert(asNormalizedPath(`foo\bar\\baz\\\`).array == `foo\bar\baz`);
2134 assert(asNormalizedPath(`\foo\bar\baz`).array == `\foo\bar\baz`);
2135 assert(asNormalizedPath(`\foo\..\\bar\.\baz`).array == `\bar\baz`);
2136 assert(asNormalizedPath(`\foo\..\bar\baz`).array == `\bar\baz`);
2137 assert(asNormalizedPath(`\foo\..\..\\bar\baz`).array == `\bar\baz`);
2138
2139 assert(asNormalizedPath(`\foo\bar\..\baz`).array == `\foo\baz`);
2140 assert(asNormalizedPath(`\foo\bar\../../baz`).array == `\baz`);
2141 assert(asNormalizedPath(`\foo\bar\..\.\/baz\..\wee\`).array == `\foo\wee`);
2142
2143 assert(asNormalizedPath(`c:\foo\bar`).array == `c:\foo\bar`);
2144 assert(asNormalizedPath(`c:foo\bar\baz`).array == `c:foo\bar\baz`);
2145 assert(asNormalizedPath(`c:foo\bar\baz`).array == `c:foo\bar\baz`);
2146 assert(asNormalizedPath(`c:foo\bar\\baz\\\`).array == `c:foo\bar\baz`);
2147 assert(asNormalizedPath(`c:\foo\bar\baz`).array == `c:\foo\bar\baz`);
2148
2149 assert(asNormalizedPath(`c:\foo\..\\bar\.\baz`).array == `c:\bar\baz`);
2150 assert(asNormalizedPath(`c:\foo\..\bar\baz`).array == `c:\bar\baz`);
2151 assert(asNormalizedPath(`c:\foo\..\..\\bar\baz`).array == `c:\bar\baz`);
2152 assert(asNormalizedPath(`c:\foo\bar\..\baz`).array == `c:\foo\baz`);
2153 assert(asNormalizedPath(`c:\foo\bar\..\..\baz`).array == `c:\baz`);
2154 assert(asNormalizedPath(`c:\foo\bar\..\.\\baz\..\wee\`).array == `c:\foo\wee`);
2155 assert(asNormalizedPath(`\\server\share\foo\bar`).array == `\\server\share\foo\bar`);
2156 assert(asNormalizedPath(`\\server\share\\foo\bar`).array == `\\server\share\foo\bar`);
2157 assert(asNormalizedPath(`\\server\share\foo\bar\baz`).array == `\\server\share\foo\bar\baz`);
2158 assert(asNormalizedPath(`\\server\share\foo\..\\bar\.\baz`).array == `\\server\share\bar\baz`);
2159 assert(asNormalizedPath(`\\server\share\foo\..\bar\baz`).array == `\\server\share\bar\baz`);
2160 assert(asNormalizedPath(`\\server\share\foo\..\..\\bar\baz`).array == `\\server\share\bar\baz`);
2161 assert(asNormalizedPath(`\\server\share\foo\bar\..\baz`).array == `\\server\share\foo\baz`);
2162 assert(asNormalizedPath(`\\server\share\foo\bar\..\..\baz`).array == `\\server\share\baz`);
2163 assert(asNormalizedPath(`\\server\share\foo\bar\..\.\\baz\..\wee\`).array == `\\server\share\foo\wee`);
2164
2165 static assert(asNormalizedPath(`\foo\..\..\\bar\baz`).array == `\bar\baz`);
2166
2167 assert(asNormalizedPath("foo//bar").array == `foo\bar`);
2168
2169 //Curent dir path
2170 assert(asNormalizedPath(`.\`).array == ".");
2171 assert(asNormalizedPath(`.\.\`).array == ".");
2172 assert(asNormalizedPath(`.\foo\..`).array == ".");
2173 assert(asNormalizedPath(`foo\..`).array == ".");
2174 }
2175 else static assert(0);
2176 }
2177
2178 @safe unittest
2179 {
2180 import std.array;
2181
version(Posix)2182 version (Posix)
2183 {
2184 // Trivial
2185 assert(asNormalizedPath("").empty);
2186 assert(asNormalizedPath("foo/bar").array == "foo/bar");
2187
2188 // Correct handling of leading slashes
2189 assert(asNormalizedPath("/").array == "/");
2190 assert(asNormalizedPath("///").array == "/");
2191 assert(asNormalizedPath("////").array == "/");
2192 assert(asNormalizedPath("/foo/bar").array == "/foo/bar");
2193 assert(asNormalizedPath("//foo/bar").array == "/foo/bar");
2194 assert(asNormalizedPath("///foo/bar").array == "/foo/bar");
2195 assert(asNormalizedPath("////foo/bar").array == "/foo/bar");
2196
2197 // Correct handling of single-dot symbol (current directory)
2198 assert(asNormalizedPath("/./foo").array == "/foo");
2199 assert(asNormalizedPath("/foo/./bar").array == "/foo/bar");
2200
2201 assert(asNormalizedPath("./foo").array == "foo");
2202 assert(asNormalizedPath("././foo").array == "foo");
2203 assert(asNormalizedPath("foo/././bar").array == "foo/bar");
2204
2205 // Correct handling of double-dot symbol (previous directory)
2206 assert(asNormalizedPath("/foo/../bar").array == "/bar");
2207 assert(asNormalizedPath("/foo/../../bar").array == "/bar");
2208 assert(asNormalizedPath("/../foo").array == "/foo");
2209 assert(asNormalizedPath("/../../foo").array == "/foo");
2210 assert(asNormalizedPath("/foo/..").array == "/");
2211 assert(asNormalizedPath("/foo/../..").array == "/");
2212
2213 assert(asNormalizedPath("foo/../bar").array == "bar");
2214 assert(asNormalizedPath("foo/../../bar").array == "../bar");
2215 assert(asNormalizedPath("../foo").array == "../foo");
2216 assert(asNormalizedPath("../../foo").array == "../../foo");
2217 assert(asNormalizedPath("../foo/../bar").array == "../bar");
2218 assert(asNormalizedPath(".././../foo").array == "../../foo");
2219 assert(asNormalizedPath("foo/bar/..").array == "foo");
2220 assert(asNormalizedPath("/foo/../..").array == "/");
2221
2222 // The ultimate path
2223 assert(asNormalizedPath("/foo/../bar//./../...///baz//").array == "/.../baz");
2224 static assert(asNormalizedPath("/foo/../bar//./../...///baz//").array == "/.../baz");
2225 }
version(Windows)2226 else version (Windows)
2227 {
2228 // Trivial
2229 assert(asNormalizedPath("").empty);
2230 assert(asNormalizedPath(`foo\bar`).array == `foo\bar`);
2231 assert(asNormalizedPath("foo/bar").array == `foo\bar`);
2232
2233 // Correct handling of absolute paths
2234 assert(asNormalizedPath("/").array == `\`);
2235 assert(asNormalizedPath(`\`).array == `\`);
2236 assert(asNormalizedPath(`\\\`).array == `\`);
2237 assert(asNormalizedPath(`\\\\`).array == `\`);
2238 assert(asNormalizedPath(`\foo\bar`).array == `\foo\bar`);
2239 assert(asNormalizedPath(`\\foo`).array == `\\foo`);
2240 assert(asNormalizedPath(`\\foo\\`).array == `\\foo`);
2241 assert(asNormalizedPath(`\\foo/bar`).array == `\\foo\bar`);
2242 assert(asNormalizedPath(`\\\foo\bar`).array == `\foo\bar`);
2243 assert(asNormalizedPath(`\\\\foo\bar`).array == `\foo\bar`);
2244 assert(asNormalizedPath(`c:\`).array == `c:\`);
2245 assert(asNormalizedPath(`c:\foo\bar`).array == `c:\foo\bar`);
2246 assert(asNormalizedPath(`c:\\foo\bar`).array == `c:\foo\bar`);
2247
2248 // Correct handling of single-dot symbol (current directory)
2249 assert(asNormalizedPath(`\./foo`).array == `\foo`);
2250 assert(asNormalizedPath(`\foo/.\bar`).array == `\foo\bar`);
2251
2252 assert(asNormalizedPath(`.\foo`).array == `foo`);
2253 assert(asNormalizedPath(`./.\foo`).array == `foo`);
2254 assert(asNormalizedPath(`foo\.\./bar`).array == `foo\bar`);
2255
2256 // Correct handling of double-dot symbol (previous directory)
2257 assert(asNormalizedPath(`\foo\..\bar`).array == `\bar`);
2258 assert(asNormalizedPath(`\foo\../..\bar`).array == `\bar`);
2259 assert(asNormalizedPath(`\..\foo`).array == `\foo`);
2260 assert(asNormalizedPath(`\..\..\foo`).array == `\foo`);
2261 assert(asNormalizedPath(`\foo\..`).array == `\`);
2262 assert(asNormalizedPath(`\foo\../..`).array == `\`);
2263
2264 assert(asNormalizedPath(`foo\..\bar`).array == `bar`);
2265 assert(asNormalizedPath(`foo\..\../bar`).array == `..\bar`);
2266
2267 assert(asNormalizedPath(`..\foo`).array == `..\foo`);
2268 assert(asNormalizedPath(`..\..\foo`).array == `..\..\foo`);
2269 assert(asNormalizedPath(`..\foo\..\bar`).array == `..\bar`);
2270 assert(asNormalizedPath(`..\.\..\foo`).array == `..\..\foo`);
2271 assert(asNormalizedPath(`foo\bar\..`).array == `foo`);
2272 assert(asNormalizedPath(`\foo\..\..`).array == `\`);
2273 assert(asNormalizedPath(`c:\foo\..\..`).array == `c:\`);
2274
2275 // Correct handling of non-root path with drive specifier
2276 assert(asNormalizedPath(`c:foo`).array == `c:foo`);
2277 assert(asNormalizedPath(`c:..\foo\.\..\bar`).array == `c:..\bar`);
2278
2279 // The ultimate path
2280 assert(asNormalizedPath(`c:\foo\..\bar\\.\..\...\\\baz\\`).array == `c:\...\baz`);
2281 static assert(asNormalizedPath(`c:\foo\..\bar\\.\..\...\\\baz\\`).array == `c:\...\baz`);
2282 }
2283 else static assert(false);
2284 }
2285
2286 /** Slice up a path into its elements.
2287
2288 Params:
2289 path = string or slicable random access range
2290
2291 Returns:
2292 bidirectional range of slices of `path`
2293 */
2294 auto pathSplitter(R)(R path)
2295 if ((isRandomAccessRange!R && hasSlicing!R ||
2296 isNarrowString!R) &&
2297 !isConvertibleToString!R)
2298 {
2299 static struct PathSplitter
2300 {
emptyPathSplitter2301 @property bool empty() const { return pe == 0; }
2302
frontPathSplitter2303 @property R front()
2304 {
2305 assert(!empty);
2306 return _path[fs .. fe];
2307 }
2308
popFrontPathSplitter2309 void popFront()
2310 {
2311 assert(!empty);
2312 if (ps == pe)
2313 {
2314 if (fs == bs && fe == be)
2315 {
2316 pe = 0;
2317 }
2318 else
2319 {
2320 fs = bs;
2321 fe = be;
2322 }
2323 }
2324 else
2325 {
2326 fs = ps;
2327 fe = fs;
2328 while (fe < pe && !isDirSeparator(_path[fe]))
2329 ++fe;
2330 ps = ltrim(fe, pe);
2331 }
2332 }
2333
backPathSplitter2334 @property R back()
2335 {
2336 assert(!empty);
2337 return _path[bs .. be];
2338 }
2339
popBackPathSplitter2340 void popBack()
2341 {
2342 assert(!empty);
2343 if (ps == pe)
2344 {
2345 if (fs == bs && fe == be)
2346 {
2347 pe = 0;
2348 }
2349 else
2350 {
2351 bs = fs;
2352 be = fe;
2353 }
2354 }
2355 else
2356 {
2357 bs = pe;
2358 be = bs;
2359 while (bs > ps && !isDirSeparator(_path[bs - 1]))
2360 --bs;
2361 pe = rtrim(ps, bs);
2362 }
2363 }
savePathSplitter2364 @property auto save() { return this; }
2365
2366
2367 private:
2368 R _path;
2369 size_t ps, pe;
2370 size_t fs, fe;
2371 size_t bs, be;
2372
thisPathSplitter2373 this(R p)
2374 {
2375 if (p.empty)
2376 {
2377 pe = 0;
2378 return;
2379 }
2380 _path = p;
2381
2382 ps = 0;
2383 pe = _path.length;
2384
2385 // If path is rooted, first element is special
2386 version (Windows)
2387 {
2388 if (isUNC(_path))
2389 {
2390 auto i = uncRootLength(_path);
2391 fs = 0;
2392 fe = i;
2393 ps = ltrim(fe, pe);
2394 }
2395 else if (isDriveRoot(_path))
2396 {
2397 fs = 0;
2398 fe = 3;
2399 ps = ltrim(fe, pe);
2400 }
2401 else if (_path.length >= 1 && isDirSeparator(_path[0]))
2402 {
2403 fs = 0;
2404 fe = 1;
2405 ps = ltrim(fe, pe);
2406 }
2407 else
2408 {
2409 assert(!isRooted(_path));
2410 popFront();
2411 }
2412 }
2413 else version (Posix)
2414 {
2415 if (_path.length >= 1 && isDirSeparator(_path[0]))
2416 {
2417 fs = 0;
2418 fe = 1;
2419 ps = ltrim(fe, pe);
2420 }
2421 else
2422 {
2423 popFront();
2424 }
2425 }
2426 else static assert(0);
2427
2428 if (ps == pe)
2429 {
2430 bs = fs;
2431 be = fe;
2432 }
2433 else
2434 {
2435 pe = rtrim(ps, pe);
2436 popBack();
2437 }
2438 }
2439
ltrimPathSplitter2440 size_t ltrim(size_t s, size_t e)
2441 {
2442 while (s < e && isDirSeparator(_path[s]))
2443 ++s;
2444 return s;
2445 }
2446
rtrimPathSplitter2447 size_t rtrim(size_t s, size_t e)
2448 {
2449 while (s < e && isDirSeparator(_path[e - 1]))
2450 --e;
2451 return e;
2452 }
2453 }
2454
2455 return PathSplitter(path);
2456 }
2457
2458 ///
2459 @safe unittest
2460 {
2461 import std.algorithm.comparison : equal;
2462 import std.conv : to;
2463
2464 assert(equal(pathSplitter("/"), ["/"]));
2465 assert(equal(pathSplitter("/foo/bar"), ["/", "foo", "bar"]));
2466 assert(equal(pathSplitter("foo/../bar//./"), ["foo", "..", "bar", "."]));
2467
version(Posix)2468 version (Posix)
2469 {
2470 assert(equal(pathSplitter("//foo/bar"), ["/", "foo", "bar"]));
2471 }
2472
version(Windows)2473 version (Windows)
2474 {
2475 assert(equal(pathSplitter(`foo\..\bar\/.\`), ["foo", "..", "bar", "."]));
2476 assert(equal(pathSplitter("c:"), ["c:"]));
2477 assert(equal(pathSplitter(`c:\foo\bar`), [`c:\`, "foo", "bar"]));
2478 assert(equal(pathSplitter(`c:foo\bar`), ["c:foo", "bar"]));
2479 }
2480 }
2481
2482 auto pathSplitter(R)(auto ref R path)
2483 if (isConvertibleToString!R)
2484 {
2485 return pathSplitter!(StringTypeOf!R)(path);
2486 }
2487
2488 @safe unittest
2489 {
2490 import std.algorithm.comparison : equal;
2491 assert(testAliasedString!pathSplitter("/"));
2492 }
2493
2494 @safe unittest
2495 {
2496 // equal2 verifies that the range is the same both ways, i.e.
2497 // through front/popFront and back/popBack.
2498 import std.algorithm;
2499 import std.range;
equal2(R1,R2)2500 bool equal2(R1, R2)(R1 r1, R2 r2)
2501 {
2502 static assert(isBidirectionalRange!R1);
2503 return equal(r1, r2) && equal(retro(r1), retro(r2));
2504 }
2505
2506 assert(pathSplitter("").empty);
2507
2508 // Root directories
2509 assert(equal2(pathSplitter("/"), ["/"]));
2510 assert(equal2(pathSplitter("//"), ["/"]));
2511 assert(equal2(pathSplitter("///"w), ["/"w]));
2512
2513 // Absolute paths
2514 assert(equal2(pathSplitter("/foo/bar".dup), ["/", "foo", "bar"]));
2515
2516 // General
2517 assert(equal2(pathSplitter("foo/bar"d.dup), ["foo"d, "bar"d]));
2518 assert(equal2(pathSplitter("foo//bar"), ["foo", "bar"]));
2519 assert(equal2(pathSplitter("foo/bar//"w), ["foo"w, "bar"w]));
2520 assert(equal2(pathSplitter("foo/../bar//./"d), ["foo"d, ".."d, "bar"d, "."d]));
2521
2522 // save()
2523 auto ps1 = pathSplitter("foo/bar/baz");
2524 auto ps2 = ps1.save;
2525 ps1.popFront();
2526 assert(equal2(ps1, ["bar", "baz"]));
2527 assert(equal2(ps2, ["foo", "bar", "baz"]));
2528
2529 // Platform specific
version(Posix)2530 version (Posix)
2531 {
2532 assert(equal2(pathSplitter("//foo/bar"w.dup), ["/"w, "foo"w, "bar"w]));
2533 }
version(Windows)2534 version (Windows)
2535 {
2536 assert(equal2(pathSplitter(`\`), [`\`]));
2537 assert(equal2(pathSplitter(`foo\..\bar\/.\`), ["foo", "..", "bar", "."]));
2538 assert(equal2(pathSplitter("c:"), ["c:"]));
2539 assert(equal2(pathSplitter(`c:\foo\bar`), [`c:\`, "foo", "bar"]));
2540 assert(equal2(pathSplitter(`c:foo\bar`), ["c:foo", "bar"]));
2541 assert(equal2(pathSplitter(`\\foo\bar`), [`\\foo\bar`]));
2542 assert(equal2(pathSplitter(`\\foo\bar\\`), [`\\foo\bar`]));
2543 assert(equal2(pathSplitter(`\\foo\bar\baz`), [`\\foo\bar`, "baz"]));
2544 }
2545
2546 import std.exception;
2547 assertCTFEable!(
2548 {
2549 assert(equal(pathSplitter("/foo/bar".dup), ["/", "foo", "bar"]));
2550 });
2551
2552 static assert(is(typeof(pathSplitter!(const(char)[])(null).front) == const(char)[]));
2553
2554 import std.utf : byDchar;
2555 assert(equal2(pathSplitter("foo/bar"d.byDchar), ["foo"d, "bar"d]));
2556 }
2557
2558
2559
2560
2561 /** Determines whether a path starts at a root directory.
2562
2563 Params:
2564 path = A path name.
2565 Returns:
2566 Whether a path starts at a root directory.
2567
2568 On POSIX, this function returns true if and only if the path starts
2569 with a slash (/).
2570
2571 On Windows, this function returns true if the path starts at
2572 the root directory of the current drive, of some other drive,
2573 or of a network drive.
2574 */
2575 bool isRooted(R)(R path)
2576 if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
2577 is(StringTypeOf!R))
2578 {
2579 if (path.length >= 1 && isDirSeparator(path[0])) return true;
2580 version (Posix) return false;
2581 else version (Windows) return isAbsolute!(BaseOf!R)(path);
2582 }
2583
2584 ///
2585 @safe unittest
2586 {
version(Posix)2587 version (Posix)
2588 {
2589 assert( isRooted("/"));
2590 assert( isRooted("/foo"));
2591 assert(!isRooted("foo"));
2592 assert(!isRooted("../foo"));
2593 }
2594
version(Windows)2595 version (Windows)
2596 {
2597 assert( isRooted(`\`));
2598 assert( isRooted(`\foo`));
2599 assert( isRooted(`d:\foo`));
2600 assert( isRooted(`\\foo\bar`));
2601 assert(!isRooted("foo"));
2602 assert(!isRooted("d:foo"));
2603 }
2604 }
2605
2606 @safe unittest
2607 {
2608 assert(isRooted("/"));
2609 assert(isRooted("/foo"));
2610 assert(!isRooted("foo"));
2611 assert(!isRooted("../foo"));
2612
version(Windows)2613 version (Windows)
2614 {
2615 assert(isRooted(`\`));
2616 assert(isRooted(`\foo`));
2617 assert(isRooted(`d:\foo`));
2618 assert(isRooted(`\\foo\bar`));
2619 assert(!isRooted("foo"));
2620 assert(!isRooted("d:foo"));
2621 }
2622
2623 static assert(isRooted("/foo"));
2624 static assert(!isRooted("foo"));
2625
2626 static struct DirEntry { string s; alias s this; }
2627 assert(!isRooted(DirEntry("foo")));
2628 }
2629
2630 /** Determines whether a path is absolute or not.
2631
2632 Params: path = A path name.
2633
2634 Returns: Whether a path is absolute or not.
2635
2636 Example:
2637 On POSIX, an absolute path starts at the root directory.
2638 (In fact, `_isAbsolute` is just an alias for $(LREF isRooted).)
2639 ---
2640 version (Posix)
2641 {
2642 assert(isAbsolute("/"));
2643 assert(isAbsolute("/foo"));
2644 assert(!isAbsolute("foo"));
2645 assert(!isAbsolute("../foo"));
2646 }
2647 ---
2648
2649 On Windows, an absolute path starts at the root directory of
2650 a specific drive. Hence, it must start with $(D `d:\`) or $(D `d:/`),
2651 where `d` is the drive letter. Alternatively, it may be a
2652 network path, i.e. a path starting with a double (back)slash.
2653 ---
2654 version (Windows)
2655 {
2656 assert(isAbsolute(`d:\`));
2657 assert(isAbsolute(`d:\foo`));
2658 assert(isAbsolute(`\\foo\bar`));
2659 assert(!isAbsolute(`\`));
2660 assert(!isAbsolute(`\foo`));
2661 assert(!isAbsolute("d:foo"));
2662 }
2663 ---
2664 */
version(StdDdoc)2665 version (StdDdoc)
2666 {
2667 bool isAbsolute(R)(R path) pure nothrow @safe
2668 if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
2669 is(StringTypeOf!R));
2670 }
version(Windows)2671 else version (Windows)
2672 {
2673 bool isAbsolute(R)(R path)
2674 if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
2675 is(StringTypeOf!R))
2676 {
2677 return isDriveRoot!(BaseOf!R)(path) || isUNC!(BaseOf!R)(path);
2678 }
2679 }
version(Posix)2680 else version (Posix)
2681 {
2682 alias isAbsolute = isRooted;
2683 }
2684
2685
2686 @safe unittest
2687 {
2688 assert(!isAbsolute("foo"));
2689 assert(!isAbsolute("../foo"w));
2690 static assert(!isAbsolute("foo"));
2691
version(Posix)2692 version (Posix)
2693 {
2694 assert(isAbsolute("/"d));
2695 assert(isAbsolute("/foo".dup));
2696 static assert(isAbsolute("/foo"));
2697 }
2698
version(Windows)2699 version (Windows)
2700 {
2701 assert(isAbsolute("d:\\"w));
2702 assert(isAbsolute("d:\\foo"d));
2703 assert(isAbsolute("\\\\foo\\bar"));
2704 assert(!isAbsolute("\\"w.dup));
2705 assert(!isAbsolute("\\foo"d.dup));
2706 assert(!isAbsolute("d:"));
2707 assert(!isAbsolute("d:foo"));
2708 static assert(isAbsolute(`d:\foo`));
2709 }
2710
2711 {
2712 auto r = MockRange!(immutable(char))(`../foo`);
2713 assert(!r.isAbsolute());
2714 }
2715
2716 static struct DirEntry { string s; alias s this; }
2717 assert(!isAbsolute(DirEntry("foo")));
2718 }
2719
2720
2721
2722
2723 /** Transforms `path` into an absolute path.
2724
2725 The following algorithm is used:
2726 $(OL
2727 $(LI If `path` is empty, return `null`.)
2728 $(LI If `path` is already absolute, return it.)
2729 $(LI Otherwise, append `path` to `base` and return
2730 the result. If `base` is not specified, the current
2731 working directory is used.)
2732 )
2733 The function allocates memory if and only if it gets to the third stage
2734 of this algorithm.
2735
2736 Params:
2737 path = the relative path to transform
2738 base = the base directory of the relative path
2739
2740 Returns:
2741 string of transformed path
2742
2743 Throws:
2744 `Exception` if the specified _base directory is not absolute.
2745
2746 See_Also:
2747 $(LREF asAbsolutePath) which does not allocate
2748 */
2749 string absolutePath(string path, lazy string base = getcwd())
2750 @safe pure
2751 {
2752 import std.array : array;
2753 if (path.empty) return null;
2754 if (isAbsolute(path)) return path;
2755 auto baseVar = base;
2756 if (!isAbsolute(baseVar)) throw new Exception("Base directory must be absolute");
2757 return chainPath(baseVar, path).array;
2758 }
2759
2760 ///
2761 @safe unittest
2762 {
version(Posix)2763 version (Posix)
2764 {
2765 assert(absolutePath("some/file", "/foo/bar") == "/foo/bar/some/file");
2766 assert(absolutePath("../file", "/foo/bar") == "/foo/bar/../file");
2767 assert(absolutePath("/some/file", "/foo/bar") == "/some/file");
2768 }
2769
version(Windows)2770 version (Windows)
2771 {
2772 assert(absolutePath(`some\file`, `c:\foo\bar`) == `c:\foo\bar\some\file`);
2773 assert(absolutePath(`..\file`, `c:\foo\bar`) == `c:\foo\bar\..\file`);
2774 assert(absolutePath(`c:\some\file`, `c:\foo\bar`) == `c:\some\file`);
2775 assert(absolutePath(`\`, `c:\`) == `c:\`);
2776 assert(absolutePath(`\some\file`, `c:\foo\bar`) == `c:\some\file`);
2777 }
2778 }
2779
2780 @safe unittest
2781 {
version(Posix)2782 version (Posix)
2783 {
2784 static assert(absolutePath("some/file", "/foo/bar") == "/foo/bar/some/file");
2785 }
2786
version(Windows)2787 version (Windows)
2788 {
2789 static assert(absolutePath(`some\file`, `c:\foo\bar`) == `c:\foo\bar\some\file`);
2790 }
2791
2792 import std.exception;
2793 assertThrown(absolutePath("bar", "foo"));
2794 }
2795
2796 /** Transforms `path` into an absolute path.
2797
2798 The following algorithm is used:
2799 $(OL
2800 $(LI If `path` is empty, return `null`.)
2801 $(LI If `path` is already absolute, return it.)
2802 $(LI Otherwise, append `path` to the current working directory,
2803 which allocates memory.)
2804 )
2805
2806 Params:
2807 path = the relative path to transform
2808
2809 Returns:
2810 the transformed path as a lazy range
2811
2812 See_Also:
2813 $(LREF absolutePath) which returns an allocated string
2814 */
2815 auto asAbsolutePath(R)(R path)
2816 if ((isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
2817 isNarrowString!R) &&
2818 !isConvertibleToString!R)
2819 {
2820 import std.file : getcwd;
2821 string base = null;
2822 if (!path.empty && !isAbsolute(path))
2823 base = getcwd();
2824 return chainPath(base, path);
2825 }
2826
2827 ///
2828 @system unittest
2829 {
2830 import std.array;
2831 assert(asAbsolutePath(cast(string) null).array == "");
version(Posix)2832 version (Posix)
2833 {
2834 assert(asAbsolutePath("/foo").array == "/foo");
2835 }
version(Windows)2836 version (Windows)
2837 {
2838 assert(asAbsolutePath("c:/foo").array == "c:/foo");
2839 }
2840 asAbsolutePath("foo");
2841 }
2842
2843 auto asAbsolutePath(R)(auto ref R path)
2844 if (isConvertibleToString!R)
2845 {
2846 return asAbsolutePath!(StringTypeOf!R)(path);
2847 }
2848
2849 @system unittest
2850 {
2851 assert(testAliasedString!asAbsolutePath(null));
2852 }
2853
2854 /** Translates `path` into a relative path.
2855
2856 The returned path is relative to `base`, which is by default
2857 taken to be the current working directory. If specified,
2858 `base` must be an absolute path, and it is always assumed
2859 to refer to a directory. If `path` and `base` refer to
2860 the same directory, the function returns $(D `.`).
2861
2862 The following algorithm is used:
2863 $(OL
2864 $(LI If `path` is a relative directory, return it unaltered.)
2865 $(LI Find a common root between `path` and `base`.
2866 If there is no common root, return `path` unaltered.)
2867 $(LI Prepare a string with as many $(D `../`) or $(D `..\`) as
2868 necessary to reach the common root from base path.)
2869 $(LI Append the remaining segments of `path` to the string
2870 and return.)
2871 )
2872
2873 In the second step, path components are compared using `filenameCmp!cs`,
2874 where `cs` is an optional template parameter determining whether
2875 the comparison is case sensitive or not. See the
2876 $(LREF filenameCmp) documentation for details.
2877
2878 This function allocates memory.
2879
2880 Params:
2881 cs = Whether matching path name components against the base path should
2882 be case-sensitive or not.
2883 path = A path name.
2884 base = The base path to construct the relative path from.
2885
2886 Returns: The relative path.
2887
2888 See_Also:
2889 $(LREF asRelativePath) which does not allocate memory
2890
2891 Throws:
2892 `Exception` if the specified _base directory is not absolute.
2893 */
2894 string relativePath(CaseSensitive cs = CaseSensitive.osDefault)
2895 (string path, lazy string base = getcwd())
2896 {
2897 if (!isAbsolute(path))
2898 return path;
2899 auto baseVar = base;
2900 if (!isAbsolute(baseVar))
2901 throw new Exception("Base directory must be absolute");
2902
2903 import std.conv : to;
2904 return asRelativePath!cs(path, baseVar).to!string;
2905 }
2906
2907 ///
2908 @safe unittest
2909 {
2910 assert(relativePath("foo") == "foo");
2911
version(Posix)2912 version (Posix)
2913 {
2914 assert(relativePath("foo", "/bar") == "foo");
2915 assert(relativePath("/foo/bar", "/foo/bar") == ".");
2916 assert(relativePath("/foo/bar", "/foo/baz") == "../bar");
2917 assert(relativePath("/foo/bar/baz", "/foo/woo/wee") == "../../bar/baz");
2918 assert(relativePath("/foo/bar/baz", "/foo/bar") == "baz");
2919 }
version(Windows)2920 version (Windows)
2921 {
2922 assert(relativePath("foo", `c:\bar`) == "foo");
2923 assert(relativePath(`c:\foo\bar`, `c:\foo\bar`) == ".");
2924 assert(relativePath(`c:\foo\bar`, `c:\foo\baz`) == `..\bar`);
2925 assert(relativePath(`c:\foo\bar\baz`, `c:\foo\woo\wee`) == `..\..\bar\baz`);
2926 assert(relativePath(`c:\foo\bar\baz`, `c:\foo\bar`) == "baz");
2927 assert(relativePath(`c:\foo\bar`, `d:\foo`) == `c:\foo\bar`);
2928 }
2929 }
2930
2931 @safe unittest
2932 {
2933 import std.exception;
2934 assert(relativePath("foo") == "foo");
version(Posix)2935 version (Posix)
2936 {
2937 relativePath("/foo");
2938 assert(relativePath("/foo/bar", "/foo/baz") == "../bar");
2939 assertThrown(relativePath("/foo", "bar"));
2940 }
version(Windows)2941 else version (Windows)
2942 {
2943 relativePath(`\foo`);
2944 assert(relativePath(`c:\foo\bar\baz`, `c:\foo\bar`) == "baz");
2945 assertThrown(relativePath(`c:\foo`, "bar"));
2946 }
2947 else static assert(0);
2948 }
2949
2950 /** Transforms `path` into a path relative to `base`.
2951
2952 The returned path is relative to `base`, which is usually
2953 the current working directory.
2954 `base` must be an absolute path, and it is always assumed
2955 to refer to a directory. If `path` and `base` refer to
2956 the same directory, the function returns `'.'`.
2957
2958 The following algorithm is used:
2959 $(OL
2960 $(LI If `path` is a relative directory, return it unaltered.)
2961 $(LI Find a common root between `path` and `base`.
2962 If there is no common root, return `path` unaltered.)
2963 $(LI Prepare a string with as many `../` or `..\` as
2964 necessary to reach the common root from base path.)
2965 $(LI Append the remaining segments of `path` to the string
2966 and return.)
2967 )
2968
2969 In the second step, path components are compared using `filenameCmp!cs`,
2970 where `cs` is an optional template parameter determining whether
2971 the comparison is case sensitive or not. See the
2972 $(LREF filenameCmp) documentation for details.
2973
2974 Params:
2975 path = path to transform
2976 base = absolute path
2977 cs = whether filespec comparisons are sensitive or not; defaults to
2978 `CaseSensitive.osDefault`
2979
2980 Returns:
2981 a random access range of the transformed path
2982
2983 See_Also:
2984 $(LREF relativePath)
2985 */
2986 auto asRelativePath(CaseSensitive cs = CaseSensitive.osDefault, R1, R2)
2987 (R1 path, R2 base)
2988 if ((isNarrowString!R1 ||
2989 (isRandomAccessRange!R1 && hasSlicing!R1 && isSomeChar!(ElementType!R1)) &&
2990 !isConvertibleToString!R1) &&
2991 (isNarrowString!R2 ||
2992 (isRandomAccessRange!R2 && hasSlicing!R2 && isSomeChar!(ElementType!R2)) &&
2993 !isConvertibleToString!R2))
2994 {
2995 bool choosePath = !isAbsolute(path);
2996
2997 // Find common root with current working directory
2998
2999 auto basePS = pathSplitter(base);
3000 auto pathPS = pathSplitter(path);
3001 choosePath |= filenameCmp!cs(basePS.front, pathPS.front) != 0;
3002
3003 basePS.popFront();
3004 pathPS.popFront();
3005
3006 import std.algorithm.comparison : mismatch;
3007 import std.algorithm.iteration : joiner;
3008 import std.array : array;
3009 import std.range.primitives : walkLength;
3010 import std.range : repeat, chain, choose;
3011 import std.utf : byCodeUnit, byChar;
3012
3013 // Remove matching prefix from basePS and pathPS
3014 auto tup = mismatch!((a, b) => filenameCmp!cs(a, b) == 0)(basePS, pathPS);
3015 basePS = tup[0];
3016 pathPS = tup[1];
3017
3018 string sep;
3019 if (basePS.empty && pathPS.empty)
3020 sep = "."; // if base == path, this is the return
3021 else if (!basePS.empty && !pathPS.empty)
3022 sep = dirSeparator;
3023
3024 // Append as many "../" as necessary to reach common base from path
3025 auto r1 = ".."
3026 .byChar
3027 .repeat(basePS.walkLength())
3028 .joiner(dirSeparator.byChar);
3029
3030 auto r2 = pathPS
3031 .joiner(dirSeparator.byChar)
3032 .byChar;
3033
3034 // Return (r1 ~ sep ~ r2)
3035 return choose(choosePath, path.byCodeUnit, chain(r1, sep.byChar, r2));
3036 }
3037
3038 ///
3039 @safe unittest
3040 {
3041 import std.array;
version(Posix)3042 version (Posix)
3043 {
3044 assert(asRelativePath("foo", "/bar").array == "foo");
3045 assert(asRelativePath("/foo/bar", "/foo/bar").array == ".");
3046 assert(asRelativePath("/foo/bar", "/foo/baz").array == "../bar");
3047 assert(asRelativePath("/foo/bar/baz", "/foo/woo/wee").array == "../../bar/baz");
3048 assert(asRelativePath("/foo/bar/baz", "/foo/bar").array == "baz");
3049 }
version(Windows)3050 else version (Windows)
3051 {
3052 assert(asRelativePath("foo", `c:\bar`).array == "foo");
3053 assert(asRelativePath(`c:\foo\bar`, `c:\foo\bar`).array == ".");
3054 assert(asRelativePath(`c:\foo\bar`, `c:\foo\baz`).array == `..\bar`);
3055 assert(asRelativePath(`c:\foo\bar\baz`, `c:\foo\woo\wee`).array == `..\..\bar\baz`);
3056 assert(asRelativePath(`c:/foo/bar/baz`, `c:\foo\woo\wee`).array == `..\..\bar\baz`);
3057 assert(asRelativePath(`c:\foo\bar\baz`, `c:\foo\bar`).array == "baz");
3058 assert(asRelativePath(`c:\foo\bar`, `d:\foo`).array == `c:\foo\bar`);
3059 assert(asRelativePath(`\\foo\bar`, `c:\foo`).array == `\\foo\bar`);
3060 }
3061 else
3062 static assert(0);
3063 }
3064
3065 @safe unittest
3066 {
version(Posix)3067 version (Posix)
3068 {
3069 assert(isBidirectionalRange!(typeof(asRelativePath("foo/bar/baz", "/foo/woo/wee"))));
3070 }
3071
version(Windows)3072 version (Windows)
3073 {
3074 assert(isBidirectionalRange!(typeof(asRelativePath(`c:\foo\bar`, `c:\foo\baz`))));
3075 }
3076 }
3077
3078 auto asRelativePath(CaseSensitive cs = CaseSensitive.osDefault, R1, R2)
3079 (auto ref R1 path, auto ref R2 base)
3080 if (isConvertibleToString!R1 || isConvertibleToString!R2)
3081 {
3082 import std.meta : staticMap;
3083 alias Types = staticMap!(convertToString, R1, R2);
3084 return asRelativePath!(cs, Types)(path, base);
3085 }
3086
3087 @safe unittest
3088 {
3089 import std.array;
3090 version (Posix)
3091 assert(asRelativePath(TestAliasedString("foo"), TestAliasedString("/bar")).array == "foo");
3092 else version (Windows)
3093 assert(asRelativePath(TestAliasedString("foo"), TestAliasedString(`c:\bar`)).array == "foo");
3094 assert(asRelativePath(TestAliasedString("foo"), "bar").array == "foo");
3095 assert(asRelativePath("foo", TestAliasedString("bar")).array == "foo");
3096 assert(asRelativePath(TestAliasedString("foo"), TestAliasedString("bar")).array == "foo");
3097 import std.utf : byDchar;
3098 assert(asRelativePath("foo"d.byDchar, TestAliasedString("bar")).array == "foo");
3099 }
3100
3101 @safe unittest
3102 {
3103 import std.array, std.utf : bCU=byCodeUnit;
version(Posix)3104 version (Posix)
3105 {
3106 assert(asRelativePath("/foo/bar/baz".bCU, "/foo/bar".bCU).array == "baz");
3107 assert(asRelativePath("/foo/bar/baz"w.bCU, "/foo/bar"w.bCU).array == "baz"w);
3108 assert(asRelativePath("/foo/bar/baz"d.bCU, "/foo/bar"d.bCU).array == "baz"d);
3109 }
version(Windows)3110 else version (Windows)
3111 {
3112 assert(asRelativePath(`\\foo\bar`.bCU, `c:\foo`.bCU).array == `\\foo\bar`);
3113 assert(asRelativePath(`\\foo\bar`w.bCU, `c:\foo`w.bCU).array == `\\foo\bar`w);
3114 assert(asRelativePath(`\\foo\bar`d.bCU, `c:\foo`d.bCU).array == `\\foo\bar`d);
3115 }
3116 }
3117
3118 /** Compares filename characters.
3119
3120 This function can perform a case-sensitive or a case-insensitive
3121 comparison. This is controlled through the `cs` template parameter
3122 which, if not specified, is given by $(LREF CaseSensitive)`.osDefault`.
3123
3124 On Windows, the backslash and slash characters ($(D `\`) and $(D `/`))
3125 are considered equal.
3126
3127 Params:
3128 cs = Case-sensitivity of the comparison.
3129 a = A filename character.
3130 b = A filename character.
3131
3132 Returns:
3133 $(D < 0) if $(D a < b),
3134 `0` if $(D a == b), and
3135 $(D > 0) if $(D a > b).
3136 */
3137 int filenameCharCmp(CaseSensitive cs = CaseSensitive.osDefault)(dchar a, dchar b)
3138 @safe pure nothrow
3139 {
3140 if (isDirSeparator(a) && isDirSeparator(b)) return 0;
3141 static if (!cs)
3142 {
3143 import std.uni : toLower;
3144 a = toLower(a);
3145 b = toLower(b);
3146 }
3147 return cast(int)(a - b);
3148 }
3149
3150 ///
3151 @safe unittest
3152 {
3153 assert(filenameCharCmp('a', 'a') == 0);
3154 assert(filenameCharCmp('a', 'b') < 0);
3155 assert(filenameCharCmp('b', 'a') > 0);
3156
version(linux)3157 version (linux)
3158 {
3159 // Same as calling filenameCharCmp!(CaseSensitive.yes)(a, b)
3160 assert(filenameCharCmp('A', 'a') < 0);
3161 assert(filenameCharCmp('a', 'A') > 0);
3162 }
version(Windows)3163 version (Windows)
3164 {
3165 // Same as calling filenameCharCmp!(CaseSensitive.no)(a, b)
3166 assert(filenameCharCmp('a', 'A') == 0);
3167 assert(filenameCharCmp('a', 'B') < 0);
3168 assert(filenameCharCmp('A', 'b') < 0);
3169 }
3170 }
3171
3172 @safe unittest
3173 {
3174 assert(filenameCharCmp!(CaseSensitive.yes)('A', 'a') < 0);
3175 assert(filenameCharCmp!(CaseSensitive.yes)('a', 'A') > 0);
3176
3177 assert(filenameCharCmp!(CaseSensitive.no)('a', 'a') == 0);
3178 assert(filenameCharCmp!(CaseSensitive.no)('a', 'b') < 0);
3179 assert(filenameCharCmp!(CaseSensitive.no)('b', 'a') > 0);
3180 assert(filenameCharCmp!(CaseSensitive.no)('A', 'a') == 0);
3181 assert(filenameCharCmp!(CaseSensitive.no)('a', 'A') == 0);
3182 assert(filenameCharCmp!(CaseSensitive.no)('a', 'B') < 0);
3183 assert(filenameCharCmp!(CaseSensitive.no)('B', 'a') > 0);
3184 assert(filenameCharCmp!(CaseSensitive.no)('A', 'b') < 0);
3185 assert(filenameCharCmp!(CaseSensitive.no)('b', 'A') > 0);
3186
3187 version (Posix) assert(filenameCharCmp('\\', '/') != 0);
3188 version (Windows) assert(filenameCharCmp('\\', '/') == 0);
3189 }
3190
3191
3192 /** Compares file names and returns
3193
3194 Individual characters are compared using `filenameCharCmp!cs`,
3195 where `cs` is an optional template parameter determining whether
3196 the comparison is case sensitive or not.
3197
3198 Treatment of invalid UTF encodings is implementation defined.
3199
3200 Params:
3201 cs = case sensitivity
3202 filename1 = range for first file name
3203 filename2 = range for second file name
3204
3205 Returns:
3206 $(D < 0) if $(D filename1 < filename2),
3207 `0` if $(D filename1 == filename2) and
3208 $(D > 0) if $(D filename1 > filename2).
3209
3210 See_Also:
3211 $(LREF filenameCharCmp)
3212 */
3213 int filenameCmp(CaseSensitive cs = CaseSensitive.osDefault, Range1, Range2)
3214 (Range1 filename1, Range2 filename2)
3215 if (isSomeFiniteCharInputRange!Range1 && !isConvertibleToString!Range1 &&
3216 isSomeFiniteCharInputRange!Range2 && !isConvertibleToString!Range2)
3217 {
3218 alias C1 = Unqual!(ElementEncodingType!Range1);
3219 alias C2 = Unqual!(ElementEncodingType!Range2);
3220
3221 static if (!cs && (C1.sizeof < 4 || C2.sizeof < 4) ||
3222 C1.sizeof != C2.sizeof)
3223 {
3224 // Case insensitive - decode so case is checkable
3225 // Different char sizes - decode to bring to common type
3226 import std.utf : byDchar;
3227 return filenameCmp!cs(filename1.byDchar, filename2.byDchar);
3228 }
3229 else static if (isSomeString!Range1 && C1.sizeof < 4 ||
3230 isSomeString!Range2 && C2.sizeof < 4)
3231 {
3232 // Avoid autodecoding
3233 import std.utf : byCodeUnit;
3234 return filenameCmp!cs(filename1.byCodeUnit, filename2.byCodeUnit);
3235 }
3236 else
3237 {
3238 for (;;)
3239 {
3240 if (filename1.empty) return -(cast(int) !filename2.empty);
3241 if (filename2.empty) return 1;
3242 const c = filenameCharCmp!cs(filename1.front, filename2.front);
3243 if (c != 0) return c;
3244 filename1.popFront();
3245 filename2.popFront();
3246 }
3247 }
3248 }
3249
3250 ///
3251 @safe unittest
3252 {
3253 assert(filenameCmp("abc", "abc") == 0);
3254 assert(filenameCmp("abc", "abd") < 0);
3255 assert(filenameCmp("abc", "abb") > 0);
3256 assert(filenameCmp("abc", "abcd") < 0);
3257 assert(filenameCmp("abcd", "abc") > 0);
3258
version(linux)3259 version (linux)
3260 {
3261 // Same as calling filenameCmp!(CaseSensitive.yes)(filename1, filename2)
3262 assert(filenameCmp("Abc", "abc") < 0);
3263 assert(filenameCmp("abc", "Abc") > 0);
3264 }
version(Windows)3265 version (Windows)
3266 {
3267 // Same as calling filenameCmp!(CaseSensitive.no)(filename1, filename2)
3268 assert(filenameCmp("Abc", "abc") == 0);
3269 assert(filenameCmp("abc", "Abc") == 0);
3270 assert(filenameCmp("Abc", "abD") < 0);
3271 assert(filenameCmp("abc", "AbB") > 0);
3272 }
3273 }
3274
3275 int filenameCmp(CaseSensitive cs = CaseSensitive.osDefault, Range1, Range2)
3276 (auto ref Range1 filename1, auto ref Range2 filename2)
3277 if (isConvertibleToString!Range1 || isConvertibleToString!Range2)
3278 {
3279 import std.meta : staticMap;
3280 alias Types = staticMap!(convertToString, Range1, Range2);
3281 return filenameCmp!(cs, Types)(filename1, filename2);
3282 }
3283
3284 @safe unittest
3285 {
3286 assert(filenameCmp!(CaseSensitive.yes)(TestAliasedString("Abc"), "abc") < 0);
3287 assert(filenameCmp!(CaseSensitive.yes)("Abc", TestAliasedString("abc")) < 0);
3288 assert(filenameCmp!(CaseSensitive.yes)(TestAliasedString("Abc"), TestAliasedString("abc")) < 0);
3289 }
3290
3291 @safe unittest
3292 {
3293 assert(filenameCmp!(CaseSensitive.yes)("Abc", "abc") < 0);
3294 assert(filenameCmp!(CaseSensitive.yes)("abc", "Abc") > 0);
3295
3296 assert(filenameCmp!(CaseSensitive.no)("abc", "abc") == 0);
3297 assert(filenameCmp!(CaseSensitive.no)("abc", "abd") < 0);
3298 assert(filenameCmp!(CaseSensitive.no)("abc", "abb") > 0);
3299 assert(filenameCmp!(CaseSensitive.no)("abc", "abcd") < 0);
3300 assert(filenameCmp!(CaseSensitive.no)("abcd", "abc") > 0);
3301 assert(filenameCmp!(CaseSensitive.no)("Abc", "abc") == 0);
3302 assert(filenameCmp!(CaseSensitive.no)("abc", "Abc") == 0);
3303 assert(filenameCmp!(CaseSensitive.no)("Abc", "abD") < 0);
3304 assert(filenameCmp!(CaseSensitive.no)("abc", "AbB") > 0);
3305
3306 version (Posix) assert(filenameCmp(`abc\def`, `abc/def`) != 0);
3307 version (Windows) assert(filenameCmp(`abc\def`, `abc/def`) == 0);
3308 }
3309
3310 /** Matches a pattern against a path.
3311
3312 Some characters of pattern have a special meaning (they are
3313 $(I meta-characters)) and can't be escaped. These are:
3314
3315 $(BOOKTABLE,
3316 $(TR $(TD `*`)
3317 $(TD Matches 0 or more instances of any character.))
3318 $(TR $(TD `?`)
3319 $(TD Matches exactly one instance of any character.))
3320 $(TR $(TD `[`$(I chars)`]`)
3321 $(TD Matches one instance of any character that appears
3322 between the brackets.))
3323 $(TR $(TD `[!`$(I chars)`]`)
3324 $(TD Matches one instance of any character that does not
3325 appear between the brackets after the exclamation mark.))
3326 $(TR $(TD `{`$(I string1)`,`$(I string2)`,`…`}`)
3327 $(TD Matches either of the specified strings.))
3328 )
3329
3330 Individual characters are compared using `filenameCharCmp!cs`,
3331 where `cs` is an optional template parameter determining whether
3332 the comparison is case sensitive or not. See the
3333 $(LREF filenameCharCmp) documentation for details.
3334
3335 Note that directory
3336 separators and dots don't stop a meta-character from matching
3337 further portions of the path.
3338
3339 Params:
3340 cs = Whether the matching should be case-sensitive
3341 path = The path to be matched against
3342 pattern = The glob pattern
3343
3344 Returns:
3345 `true` if pattern matches path, `false` otherwise.
3346
3347 See_also:
3348 $(LINK2 http://en.wikipedia.org/wiki/Glob_%28programming%29,Wikipedia: _glob (programming))
3349 */
3350 bool globMatch(CaseSensitive cs = CaseSensitive.osDefault, C, Range)
3351 (Range path, const(C)[] pattern)
3352 @safe pure nothrow
3353 if (isForwardRange!Range && !isInfinite!Range &&
3354 isSomeChar!(ElementEncodingType!Range) && !isConvertibleToString!Range &&
3355 isSomeChar!C && is(immutable C == immutable ElementEncodingType!Range))
3356 in
3357 {
3358 // Verify that pattern[] is valid
3359 import std.algorithm.searching : balancedParens;
3360 assert(balancedParens(pattern, '[', ']', 0));
3361 assert(balancedParens(pattern, '{', '}', 0));
3362 }
3363 do
3364 {
3365 alias RC = Unqual!(ElementEncodingType!Range);
3366
3367 static if (RC.sizeof == 1 && isSomeString!Range)
3368 {
3369 import std.utf : byChar;
3370 return globMatch!cs(path.byChar, pattern);
3371 }
3372 else static if (RC.sizeof == 2 && isSomeString!Range)
3373 {
3374 import std.utf : byWchar;
3375 return globMatch!cs(path.byWchar, pattern);
3376 }
3377 else
3378 {
3379 C[] pattmp;
3380 foreach (ref pi; 0 .. pattern.length)
3381 {
3382 const pc = pattern[pi];
3383 switch (pc)
3384 {
3385 case '*':
3386 if (pi + 1 == pattern.length)
3387 return true;
3388 for (; !path.empty; path.popFront())
3389 {
3390 auto p = path.save;
3391 if (globMatch!(cs, C)(p,
3392 pattern[pi + 1 .. pattern.length]))
3393 return true;
3394 }
3395 return false;
3396
3397 case '?':
3398 if (path.empty)
3399 return false;
3400 path.popFront();
3401 break;
3402
3403 case '[':
3404 if (path.empty)
3405 return false;
3406 auto nc = path.front;
3407 path.popFront();
3408 auto not = false;
3409 ++pi;
3410 if (pattern[pi] == '!')
3411 {
3412 not = true;
3413 ++pi;
3414 }
3415 auto anymatch = false;
3416 while (1)
3417 {
3418 const pc2 = pattern[pi];
3419 if (pc2 == ']')
3420 break;
3421 if (!anymatch && (filenameCharCmp!cs(nc, pc2) == 0))
3422 anymatch = true;
3423 ++pi;
3424 }
3425 if (anymatch == not)
3426 return false;
3427 break;
3428
3429 case '{':
3430 // find end of {} section
3431 auto piRemain = pi;
3432 for (; piRemain < pattern.length
3433 && pattern[piRemain] != '}'; ++piRemain)
3434 { }
3435
3436 if (piRemain < pattern.length)
3437 ++piRemain;
3438 ++pi;
3439
3440 while (pi < pattern.length)
3441 {
3442 const pi0 = pi;
3443 C pc3 = pattern[pi];
3444 // find end of current alternative
3445 for (; pi < pattern.length && pc3 != '}' && pc3 != ','; ++pi)
3446 {
3447 pc3 = pattern[pi];
3448 }
3449
3450 auto p = path.save;
3451 if (pi0 == pi)
3452 {
3453 if (globMatch!(cs, C)(p, pattern[piRemain..$]))
3454 {
3455 return true;
3456 }
3457 ++pi;
3458 }
3459 else
3460 {
3461 /* Match for:
3462 * pattern[pi0 .. pi-1] ~ pattern[piRemain..$]
3463 */
3464 if (pattmp is null)
3465 // Allocate this only once per function invocation.
3466 // Should do it with malloc/free, but that would make it impure.
3467 pattmp = new C[pattern.length];
3468
3469 const len1 = pi - 1 - pi0;
3470 pattmp[0 .. len1] = pattern[pi0 .. pi - 1];
3471
3472 const len2 = pattern.length - piRemain;
3473 pattmp[len1 .. len1 + len2] = pattern[piRemain .. $];
3474
3475 if (globMatch!(cs, C)(p, pattmp[0 .. len1 + len2]))
3476 {
3477 return true;
3478 }
3479 }
3480 if (pc3 == '}')
3481 {
3482 break;
3483 }
3484 }
3485 return false;
3486
3487 default:
3488 if (path.empty)
3489 return false;
3490 if (filenameCharCmp!cs(pc, path.front) != 0)
3491 return false;
3492 path.popFront();
3493 break;
3494 }
3495 }
3496 return path.empty;
3497 }
3498 }
3499
3500 ///
3501 @safe unittest
3502 {
3503 assert(globMatch("foo.bar", "*"));
3504 assert(globMatch("foo.bar", "*.*"));
3505 assert(globMatch(`foo/foo\bar`, "f*b*r"));
3506 assert(globMatch("foo.bar", "f???bar"));
3507 assert(globMatch("foo.bar", "[fg]???bar"));
3508 assert(globMatch("foo.bar", "[!gh]*bar"));
3509 assert(globMatch("bar.fooz", "bar.{foo,bif}z"));
3510 assert(globMatch("bar.bifz", "bar.{foo,bif}z"));
3511
version(Windows)3512 version (Windows)
3513 {
3514 // Same as calling globMatch!(CaseSensitive.no)(path, pattern)
3515 assert(globMatch("foo", "Foo"));
3516 assert(globMatch("Goo.bar", "[fg]???bar"));
3517 }
version(linux)3518 version (linux)
3519 {
3520 // Same as calling globMatch!(CaseSensitive.yes)(path, pattern)
3521 assert(!globMatch("foo", "Foo"));
3522 assert(!globMatch("Goo.bar", "[fg]???bar"));
3523 }
3524 }
3525
3526 bool globMatch(CaseSensitive cs = CaseSensitive.osDefault, C, Range)
3527 (auto ref Range path, const(C)[] pattern)
3528 @safe pure nothrow
3529 if (isConvertibleToString!Range)
3530 {
3531 return globMatch!(cs, C, StringTypeOf!Range)(path, pattern);
3532 }
3533
3534 @safe unittest
3535 {
3536 assert(testAliasedString!globMatch("foo.bar", "*"));
3537 }
3538
3539 @safe unittest
3540 {
3541 assert(globMatch!(CaseSensitive.no)("foo", "Foo"));
3542 assert(!globMatch!(CaseSensitive.yes)("foo", "Foo"));
3543
3544 assert(globMatch("foo", "*"));
3545 assert(globMatch("foo.bar"w, "*"w));
3546 assert(globMatch("foo.bar"d, "*.*"d));
3547 assert(globMatch("foo.bar", "foo*"));
3548 assert(globMatch("foo.bar"w, "f*bar"w));
3549 assert(globMatch("foo.bar"d, "f*b*r"d));
3550 assert(globMatch("foo.bar", "f???bar"));
3551 assert(globMatch("foo.bar"w, "[fg]???bar"w));
3552 assert(globMatch("foo.bar"d, "[!gh]*bar"d));
3553
3554 assert(!globMatch("foo", "bar"));
3555 assert(!globMatch("foo"w, "*.*"w));
3556 assert(!globMatch("foo.bar"d, "f*baz"d));
3557 assert(!globMatch("foo.bar", "f*b*x"));
3558 assert(!globMatch("foo.bar", "[gh]???bar"));
3559 assert(!globMatch("foo.bar"w, "[!fg]*bar"w));
3560 assert(!globMatch("foo.bar"d, "[fg]???baz"d));
3561 assert(!globMatch("foo.di", "*.d")); // test issue 6634: triggered bad assertion
3562
3563 assert(globMatch("foo.bar", "{foo,bif}.bar"));
3564 assert(globMatch("bif.bar"w, "{foo,bif}.bar"w));
3565
3566 assert(globMatch("bar.foo"d, "bar.{foo,bif}"d));
3567 assert(globMatch("bar.bif", "bar.{foo,bif}"));
3568
3569 assert(globMatch("bar.fooz"w, "bar.{foo,bif}z"w));
3570 assert(globMatch("bar.bifz"d, "bar.{foo,bif}z"d));
3571
3572 assert(globMatch("bar.foo", "bar.{biz,,baz}foo"));
3573 assert(globMatch("bar.foo"w, "bar.{biz,}foo"w));
3574 assert(globMatch("bar.foo"d, "bar.{,biz}foo"d));
3575 assert(globMatch("bar.foo", "bar.{}foo"));
3576
3577 assert(globMatch("bar.foo"w, "bar.{ar,,fo}o"w));
3578 assert(globMatch("bar.foo"d, "bar.{,ar,fo}o"d));
3579 assert(globMatch("bar.o", "bar.{,ar,fo}o"));
3580
3581 assert(!globMatch("foo", "foo?"));
3582 assert(!globMatch("foo", "foo[]"));
3583 assert(!globMatch("foo", "foob"));
3584 assert(!globMatch("foo", "foo{b}"));
3585
3586
3587 static assert(globMatch("foo.bar", "[!gh]*bar"));
3588 }
3589
3590
3591
3592
3593 /** Checks that the given file or directory name is valid.
3594
3595 The maximum length of `filename` is given by the constant
3596 `core.stdc.stdio.FILENAME_MAX`. (On Windows, this number is
3597 defined as the maximum number of UTF-16 code points, and the
3598 test will therefore only yield strictly correct results when
3599 `filename` is a string of `wchar`s.)
3600
3601 On Windows, the following criteria must be satisfied
3602 ($(LINK2 http://msdn.microsoft.com/en-us/library/aa365247(v=vs.85).aspx,source)):
3603 $(UL
3604 $(LI `filename` must not contain any characters whose integer
3605 representation is in the range 0-31.)
3606 $(LI `filename` must not contain any of the following $(I reserved
3607 characters): `<>:"/\|?*`)
3608 $(LI `filename` may not end with a space ($(D ' ')) or a period
3609 (`'.'`).)
3610 )
3611
3612 On POSIX, `filename` may not contain a forward slash (`'/'`) or
3613 the null character (`'\0'`).
3614
3615 Params:
3616 filename = string to check
3617
3618 Returns:
3619 `true` if and only if `filename` is not
3620 empty, not too long, and does not contain invalid characters.
3621
3622 */
3623 bool isValidFilename(Range)(Range filename)
3624 if ((isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range && isSomeChar!(ElementEncodingType!Range) ||
3625 isNarrowString!Range) &&
3626 !isConvertibleToString!Range)
3627 {
3628 import core.stdc.stdio : FILENAME_MAX;
3629 if (filename.length == 0 || filename.length >= FILENAME_MAX) return false;
foreach(c;filename)3630 foreach (c; filename)
3631 {
3632 version (Windows)
3633 {
3634 switch (c)
3635 {
3636 case 0:
3637 ..
3638 case 31:
3639 case '<':
3640 case '>':
3641 case ':':
3642 case '"':
3643 case '/':
3644 case '\\':
3645 case '|':
3646 case '?':
3647 case '*':
3648 return false;
3649
3650 default:
3651 break;
3652 }
3653 }
3654 else version (Posix)
3655 {
3656 if (c == 0 || c == '/') return false;
3657 }
3658 else static assert(0);
3659 }
version(Windows)3660 version (Windows)
3661 {
3662 auto last = filename[filename.length - 1];
3663 if (last == '.' || last == ' ') return false;
3664 }
3665
3666 // All criteria passed
3667 return true;
3668 }
3669
3670 ///
3671 @safe pure @nogc nothrow
3672 unittest
3673 {
3674 import std.utf : byCodeUnit;
3675
3676 assert(isValidFilename("hello.exe".byCodeUnit));
3677 }
3678
3679 bool isValidFilename(Range)(auto ref Range filename)
3680 if (isConvertibleToString!Range)
3681 {
3682 return isValidFilename!(StringTypeOf!Range)(filename);
3683 }
3684
3685 @safe unittest
3686 {
3687 assert(testAliasedString!isValidFilename("hello.exe"));
3688 }
3689
3690 @safe pure
3691 unittest
3692 {
3693 import std.conv;
3694 auto valid = ["foo"];
3695 auto invalid = ["", "foo\0bar", "foo/bar"];
3696 auto pfdep = [`foo\bar`, "*.txt"];
3697 version (Windows) invalid ~= pfdep;
3698 else version (Posix) valid ~= pfdep;
3699 else static assert(0);
3700
3701 import std.meta : AliasSeq;
3702 static foreach (T; AliasSeq!(char[], const(char)[], string, wchar[],
3703 const(wchar)[], wstring, dchar[], const(dchar)[], dstring))
3704 {
3705 foreach (fn; valid)
3706 assert(isValidFilename(to!T(fn)));
3707 foreach (fn; invalid)
3708 assert(!isValidFilename(to!T(fn)));
3709 }
3710
3711 {
3712 auto r = MockRange!(immutable(char))(`dir/file.d`);
3713 assert(!isValidFilename(r));
3714 }
3715
3716 static struct DirEntry { string s; alias s this; }
3717 assert(isValidFilename(DirEntry("file.ext")));
3718
version(Windows)3719 version (Windows)
3720 {
3721 immutable string cases = "<>:\"/\\|?*";
3722 foreach (i; 0 .. 31 + cases.length)
3723 {
3724 char[3] buf;
3725 buf[0] = 'a';
3726 buf[1] = i <= 31 ? cast(char) i : cases[i - 32];
3727 buf[2] = 'b';
3728 assert(!isValidFilename(buf[]));
3729 }
3730 }
3731 }
3732
3733
3734
3735 /** Checks whether `path` is a valid path.
3736
3737 Generally, this function checks that `path` is not empty, and that
3738 each component of the path either satisfies $(LREF isValidFilename)
3739 or is equal to `"."` or `".."`.
3740
3741 $(B It does $(I not) check whether the path points to an existing file
3742 or directory; use $(REF exists, std,file) for this purpose.)
3743
3744 On Windows, some special rules apply:
3745 $(UL
3746 $(LI If the second character of `path` is a colon (`':'`),
3747 the first character is interpreted as a drive letter, and
3748 must be in the range A-Z (case insensitive).)
3749 $(LI If `path` is on the form $(D `\\$(I server)\$(I share)\...`)
3750 (UNC path), $(LREF isValidFilename) is applied to $(I server)
3751 and $(I share) as well.)
3752 $(LI If `path` starts with $(D `\\?\`) (long UNC path), the
3753 only requirement for the rest of the string is that it does
3754 not contain the null character.)
3755 $(LI If `path` starts with $(D `\\.\`) (Win32 device namespace)
3756 this function returns `false`; such paths are beyond the scope
3757 of this module.)
3758 )
3759
3760 Params:
3761 path = string or Range of characters to check
3762
3763 Returns:
3764 true if `path` is a valid path.
3765 */
3766 bool isValidPath(Range)(Range path)
3767 if ((isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range && isSomeChar!(ElementEncodingType!Range) ||
3768 isNarrowString!Range) &&
3769 !isConvertibleToString!Range)
3770 {
3771 alias C = Unqual!(ElementEncodingType!Range);
3772
3773 if (path.empty) return false;
3774
3775 // Check whether component is "." or "..", or whether it satisfies
3776 // isValidFilename.
isValidComponent(Range component)3777 bool isValidComponent(Range component)
3778 {
3779 assert(component.length > 0);
3780 if (component[0] == '.')
3781 {
3782 if (component.length == 1) return true;
3783 else if (component.length == 2 && component[1] == '.') return true;
3784 }
3785 return isValidFilename(component);
3786 }
3787
3788 if (path.length == 1)
3789 return isDirSeparator(path[0]) || isValidComponent(path);
3790
3791 Range remainder;
version(Windows)3792 version (Windows)
3793 {
3794 if (isDirSeparator(path[0]) && isDirSeparator(path[1]))
3795 {
3796 // Some kind of UNC path
3797 if (path.length < 5)
3798 {
3799 // All valid UNC paths must have at least 5 characters
3800 return false;
3801 }
3802 else if (path[2] == '?')
3803 {
3804 // Long UNC path
3805 if (!isDirSeparator(path[3])) return false;
3806 foreach (c; path[4 .. $])
3807 {
3808 if (c == '\0') return false;
3809 }
3810 return true;
3811 }
3812 else if (path[2] == '.')
3813 {
3814 // Win32 device namespace not supported
3815 return false;
3816 }
3817 else
3818 {
3819 // Normal UNC path, i.e. \\server\share\...
3820 size_t i = 2;
3821 while (i < path.length && !isDirSeparator(path[i])) ++i;
3822 if (i == path.length || !isValidFilename(path[2 .. i]))
3823 return false;
3824 ++i; // Skip a single dir separator
3825 size_t j = i;
3826 while (j < path.length && !isDirSeparator(path[j])) ++j;
3827 if (!isValidFilename(path[i .. j])) return false;
3828 remainder = path[j .. $];
3829 }
3830 }
3831 else if (isDriveSeparator(path[1]))
3832 {
3833 import std.ascii : isAlpha;
3834 if (!isAlpha(path[0])) return false;
3835 remainder = path[2 .. $];
3836 }
3837 else
3838 {
3839 remainder = path;
3840 }
3841 }
version(Posix)3842 else version (Posix)
3843 {
3844 remainder = path;
3845 }
3846 else static assert(0);
3847 remainder = ltrimDirSeparators(remainder);
3848
3849 // Check that each component satisfies isValidComponent.
3850 while (!remainder.empty)
3851 {
3852 size_t i = 0;
3853 while (i < remainder.length && !isDirSeparator(remainder[i])) ++i;
3854 assert(i > 0);
3855 if (!isValidComponent(remainder[0 .. i])) return false;
3856 remainder = ltrimDirSeparators(remainder[i .. $]);
3857 }
3858
3859 // All criteria passed
3860 return true;
3861 }
3862
3863 ///
3864 @safe pure @nogc nothrow
3865 unittest
3866 {
3867 assert(isValidPath("/foo/bar"));
3868 assert(!isValidPath("/foo\0/bar"));
3869 assert(isValidPath("/"));
3870 assert(isValidPath("a"));
3871
version(Windows)3872 version (Windows)
3873 {
3874 assert(isValidPath(`c:\`));
3875 assert(isValidPath(`c:\foo`));
3876 assert(isValidPath(`c:\foo\.\bar\\\..\`));
3877 assert(!isValidPath(`!:\foo`));
3878 assert(!isValidPath(`c::\foo`));
3879 assert(!isValidPath(`c:\foo?`));
3880 assert(!isValidPath(`c:\foo.`));
3881
3882 assert(isValidPath(`\\server\share`));
3883 assert(isValidPath(`\\server\share\foo`));
3884 assert(isValidPath(`\\server\share\\foo`));
3885 assert(!isValidPath(`\\\server\share\foo`));
3886 assert(!isValidPath(`\\server\\share\foo`));
3887 assert(!isValidPath(`\\ser*er\share\foo`));
3888 assert(!isValidPath(`\\server\sha?e\foo`));
3889 assert(!isValidPath(`\\server\share\|oo`));
3890
3891 assert(isValidPath(`\\?\<>:"?*|/\..\.`));
3892 assert(!isValidPath("\\\\?\\foo\0bar"));
3893
3894 assert(!isValidPath(`\\.\PhysicalDisk1`));
3895 assert(!isValidPath(`\\`));
3896 }
3897
3898 import std.utf : byCodeUnit;
3899 assert(isValidPath("/foo/bar".byCodeUnit));
3900 }
3901
3902 bool isValidPath(Range)(auto ref Range path)
3903 if (isConvertibleToString!Range)
3904 {
3905 return isValidPath!(StringTypeOf!Range)(path);
3906 }
3907
3908 @safe unittest
3909 {
3910 assert(testAliasedString!isValidPath("/foo/bar"));
3911 }
3912
3913 /** Performs tilde expansion in paths on POSIX systems.
3914 On Windows, this function does nothing.
3915
3916 There are two ways of using tilde expansion in a path. One
3917 involves using the tilde alone or followed by a path separator. In
3918 this case, the tilde will be expanded with the value of the
3919 environment variable `HOME`. The second way is putting
3920 a username after the tilde (i.e. `~john/Mail`). Here,
3921 the username will be searched for in the user database
3922 (i.e. `/etc/passwd` on Unix systems) and will expand to
3923 whatever path is stored there. The username is considered the
3924 string after the tilde ending at the first instance of a path
3925 separator.
3926
3927 Note that using the `~user` syntax may give different
3928 values from just `~` if the environment variable doesn't
3929 match the value stored in the user database.
3930
3931 When the environment variable version is used, the path won't
3932 be modified if the environment variable doesn't exist or it
3933 is empty. When the database version is used, the path won't be
3934 modified if the user doesn't exist in the database or there is
3935 not enough memory to perform the query.
3936
3937 This function performs several memory allocations.
3938
3939 Params:
3940 inputPath = The path name to expand.
3941
3942 Returns:
3943 `inputPath` with the tilde expanded, or just `inputPath`
3944 if it could not be expanded.
3945 For Windows, `expandTilde` merely returns its argument `inputPath`.
3946
3947 Example:
3948 -----
3949 void processFile(string path)
3950 {
3951 // Allow calling this function with paths such as ~/foo
3952 auto fullPath = expandTilde(path);
3953 ...
3954 }
3955 -----
3956 */
3957 string expandTilde(string inputPath) @safe nothrow
3958 {
3959 version (Posix)
3960 {
3961 import core.exception : onOutOfMemoryError;
3962 import core.stdc.errno : errno, EBADF, ENOENT, EPERM, ERANGE, ESRCH;
3963 import core.stdc.stdlib : malloc, free, realloc;
3964
3965 /* Joins a path from a C string to the remainder of path.
3966
3967 The last path separator from c_path is discarded. The result
3968 is joined to path[char_pos .. length] if char_pos is smaller
3969 than length, otherwise path is not appended to c_path.
3970 */
3971 static string combineCPathWithDPath(char* c_path, string path, size_t char_pos) @trusted nothrow
3972 {
3973 import core.stdc.string : strlen;
3974 import std.exception : assumeUnique;
3975
3976 assert(c_path != null);
3977 assert(path.length > 0);
3978 assert(char_pos >= 0);
3979
3980 // Search end of C string
3981 size_t end = strlen(c_path);
3982
3983 const cPathEndsWithDirSep = end && isDirSeparator(c_path[end - 1]);
3984
3985 string cp;
3986 if (char_pos < path.length)
3987 {
3988 // Remove trailing path separator, if any (with special care for root /)
3989 if (cPathEndsWithDirSep && (end > 1 || isDirSeparator(path[char_pos])))
3990 end--;
3991
3992 // Append something from path
3993 cp = assumeUnique(c_path[0 .. end] ~ path[char_pos .. $]);
3994 }
3995 else
3996 {
3997 // Remove trailing path separator, if any (except for root /)
3998 if (cPathEndsWithDirSep && end > 1)
3999 end--;
4000
4001 // Create our own copy, as lifetime of c_path is undocumented
4002 cp = c_path[0 .. end].idup;
4003 }
4004
4005 return cp;
4006 }
4007
4008 // Replaces the tilde from path with the environment variable HOME.
4009 static string expandFromEnvironment(string path) @safe nothrow
4010 {
4011 import core.stdc.stdlib : getenv;
4012
4013 assert(path.length >= 1);
4014 assert(path[0] == '~');
4015
4016 // Get HOME and use that to replace the tilde.
4017 auto home = () @trusted { return getenv("HOME"); } ();
4018 if (home == null)
4019 return path;
4020
4021 return combineCPathWithDPath(home, path, 1);
4022 }
4023
4024 // Replaces the tilde from path with the path from the user database.
4025 static string expandFromDatabase(string path) @safe nothrow
4026 {
4027 // bionic doesn't really support this, as getpwnam_r
4028 // isn't provided and getpwnam is basically just a stub
4029 version (CRuntime_Bionic)
4030 {
4031 return path;
4032 }
4033 else
4034 {
4035 import core.sys.posix.pwd : passwd, getpwnam_r;
4036 import std.string : indexOf;
4037
4038 assert(path.length > 2 || (path.length == 2 && !isDirSeparator(path[1])));
4039 assert(path[0] == '~');
4040
4041 // Extract username, searching for path separator.
4042 auto last_char = indexOf(path, dirSeparator[0]);
4043
4044 size_t username_len = (last_char == -1) ? path.length : last_char;
4045 char[] username = new char[username_len * char.sizeof];
4046
4047 if (last_char == -1)
4048 {
4049 username[0 .. username_len - 1] = path[1 .. $];
4050 last_char = path.length + 1;
4051 }
4052 else
4053 {
4054 username[0 .. username_len - 1] = path[1 .. last_char];
4055 }
4056 username[username_len - 1] = 0;
4057
4058 assert(last_char > 1);
4059
4060 // Reserve C memory for the getpwnam_r() function.
4061 version (StdUnittest)
4062 uint extra_memory_size = 2;
4063 else
4064 uint extra_memory_size = 5 * 1024;
4065 char[] extra_memory;
4066
4067 passwd result;
4068 loop: while (1)
4069 {
4070 extra_memory.length += extra_memory_size;
4071
4072 // Obtain info from database.
4073 passwd *verify;
4074 errno = 0;
4075 auto passResult = () @trusted { return getpwnam_r(
4076 &username[0],
4077 &result,
4078 &extra_memory[0],
4079 extra_memory.length,
4080 &verify
4081 ); } ();
4082 if (passResult == 0)
4083 {
4084 // Succeeded if verify points at result
4085 if (verify == () @trusted { return &result; } ())
4086 // username is found
4087 path = combineCPathWithDPath(result.pw_dir, path, last_char);
4088 break;
4089 }
4090
4091 switch (errno)
4092 {
4093 case ERANGE:
4094 // On BSD and OSX, errno can be left at 0 instead of set to ERANGE
4095 case 0:
4096 break;
4097
4098 case ENOENT:
4099 case ESRCH:
4100 case EBADF:
4101 case EPERM:
4102 // The given name or uid was not found.
4103 break loop;
4104
4105 default:
4106 onOutOfMemoryError();
4107 }
4108
4109 // extra_memory isn't large enough
4110 import core.checkedint : mulu;
4111 bool overflow;
4112 extra_memory_size = mulu(extra_memory_size, 2, overflow);
4113 if (overflow) assert(0);
4114 }
4115 return path;
4116 }
4117 }
4118
4119 // Return early if there is no tilde in path.
4120 if (inputPath.length < 1 || inputPath[0] != '~')
4121 return inputPath;
4122
4123 if (inputPath.length == 1 || isDirSeparator(inputPath[1]))
4124 return expandFromEnvironment(inputPath);
4125 else
4126 return expandFromDatabase(inputPath);
4127 }
4128 else version (Windows)
4129 {
4130 // Put here real windows implementation.
4131 return inputPath;
4132 }
4133 else
4134 {
4135 static assert(0); // Guard. Implement on other platforms.
4136 }
4137 }
4138
4139 ///
4140 @system unittest
4141 {
4142 version (Posix)
4143 {
4144 import std.process : environment;
4145
4146 auto oldHome = environment["HOME"];
4147 scope(exit) environment["HOME"] = oldHome;
4148
4149 environment["HOME"] = "dmd/test";
4150 assert(expandTilde("~/") == "dmd/test/");
4151 assert(expandTilde("~") == "dmd/test");
4152 }
4153 }
4154
4155 @system unittest
4156 {
4157 version (Posix)
4158 {
4159 static if (__traits(compiles, { import std.process : executeShell; }))
4160 import std.process : executeShell;
4161
4162 import std.process : environment;
4163 import std.string : strip;
4164
4165 // Retrieve the current home variable.
4166 auto oldHome = environment.get("HOME");
4167
4168 // Testing when there is no environment variable.
4169 environment.remove("HOME");
4170 assert(expandTilde("~/") == "~/");
4171 assert(expandTilde("~") == "~");
4172
4173 // Testing when an environment variable is set.
4174 environment["HOME"] = "dmd/test";
4175 assert(expandTilde("~/") == "dmd/test/");
4176 assert(expandTilde("~") == "dmd/test");
4177
4178 // The same, but with a variable ending in a slash.
4179 environment["HOME"] = "dmd/test/";
4180 assert(expandTilde("~/") == "dmd/test/");
4181 assert(expandTilde("~") == "dmd/test");
4182
4183 // The same, but with a variable set to root.
4184 environment["HOME"] = "/";
4185 assert(expandTilde("~/") == "/");
4186 assert(expandTilde("~") == "/");
4187
4188 // Recover original HOME variable before continuing.
4189 if (oldHome !is null) environment["HOME"] = oldHome;
4190 else environment.remove("HOME");
4191
4192 static if (is(typeof(executeShell)))
4193 {
4194 immutable tildeUser = "~" ~ environment.get("USER");
4195 immutable path = executeShell("echo " ~ tildeUser).output.strip();
4196 immutable expTildeUser = expandTilde(tildeUser);
4197 assert(expTildeUser == path, expTildeUser);
4198 immutable expTildeUserSlash = expandTilde(tildeUser ~ "/");
4199 immutable pathSlash = path[$-1] == '/' ? path : path ~ "/";
4200 assert(expTildeUserSlash == pathSlash, expTildeUserSlash);
4201 }
4202
4203 assert(expandTilde("~Idontexist/hey") == "~Idontexist/hey");
4204 }
4205 }
4206
4207 version (StdUnittest)
4208 {
4209 private:
4210 /* Define a mock RandomAccessRange to use for unittesting.
4211 */
4212
4213 struct MockRange(C)
4214 {
4215 this(C[] array) { this.array = array; }
4216 const
4217 {
4218 @property size_t length() { return array.length; }
4219 @property bool empty() { return array.length == 0; }
4220 @property C front() { return array[0]; }
4221 @property C back() { return array[$ - 1]; }
4222 alias opDollar = length;
4223 C opIndex(size_t i) { return array[i]; }
4224 }
4225 void popFront() { array = array[1 .. $]; }
4226 void popBack() { array = array[0 .. $-1]; }
4227 MockRange!C opSlice( size_t lwr, size_t upr) const
4228 {
4229 return MockRange!C(array[lwr .. upr]);
4230 }
4231 @property MockRange save() { return this; }
4232 private:
4233 C[] array;
4234 }
4235
4236 /* Define a mock BidirectionalRange to use for unittesting.
4237 */
4238
4239 struct MockBiRange(C)
4240 {
4241 this(const(C)[] array) { this.array = array; }
4242 const
4243 {
4244 @property bool empty() { return array.length == 0; }
4245 @property C front() { return array[0]; }
4246 @property C back() { return array[$ - 1]; }
4247 @property size_t opDollar() { return array.length; }
4248 }
4249 void popFront() { array = array[1 .. $]; }
4250 void popBack() { array = array[0 .. $-1]; }
4251 @property MockBiRange save() { return this; }
4252 private:
4253 const(C)[] array;
4254 }
4255
4256 }
4257
4258 @safe unittest
4259 {
4260 static assert( isRandomAccessRange!(MockRange!(const(char))) );
4261 static assert( isBidirectionalRange!(MockBiRange!(const(char))) );
4262 }
4263
4264 private template BaseOf(R)
4265 {
4266 static if (isRandomAccessRange!R && isSomeChar!(ElementType!R))
4267 alias BaseOf = R;
4268 else
4269 alias BaseOf = StringTypeOf!R;
4270 }
4271