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