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