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