1 // Scriptlike: Utility to aid in script-like programs. 2 // Written in the D programming language. 3 4 module scriptlike.path; 5 6 import std.algorithm; 7 import std.conv; 8 import std.datetime; 9 import std.file; 10 import std.path; 11 import std.process; 12 import std.range; 13 import std.stdio; 14 import std.string; 15 import std.traits; 16 import std.typecons; 17 import std.typetuple; 18 19 /// If true, all commands will be echoed. By default, they will be 20 /// echoed to stdout, but you can override this with scriptlikeCustomEcho. 21 bool scriptlikeEcho = false; 22 23 /// Alias for backwards-compatibility. This will be deprecated in the future. 24 /// You should use scriptlikeEcho insetad. 25 alias scriptlikeTraceCommands = scriptlikeEcho; 26 27 /++ 28 If true, then run, tryRun, file write, file append, and all the echoable 29 commands that modify the filesystem will be echoed to stdout (regardless 30 of scriptlikeEcho) and NOT actually executed. 31 32 Warning! This is NOT a "set it and forget it" switch. You must still take 33 care to write your script in a way that's dryrun-safe. Two things to remember: 34 35 1. ONLY Scriptlike's functions will obey this setting. Calling Phobos 36 functions directly will BYPASS this setting. 37 38 2. If part of your script relies on a command having ACTUALLY been run, then 39 that command will fail. You must avoid that situation or work around it. 40 For example: 41 42 --------------------- 43 run(`date > tempfile`); 44 45 // The following will FAIL or behave INCORRECTLY in dryrun mode: 46 auto data = cast(string)read("tempfile"); 47 run("echo "~data); 48 --------------------- 49 50 That may be an unrealistic example, but it demonstrates the problem: Normally, 51 the code above should run fine (at least on posix). But in dryrun mode, 52 "date" will not actually be run. Therefore, tempfile will neither be created 53 nor overwritten. Result: Either an exception reading a non-existent file, 54 or outdated information will be displayed. 55 56 Scriptlike cannot anticipate or handle such situations. So it's up to you to 57 make sure your script is dryrun-safe. 58 +/ 59 bool scriptlikeDryRun = false; 60 61 /++ 62 By default, scriptlikeEcho and scriptlikeDryRun echo to stdout. 63 You can override this behavior by setting scriptlikeCustomEcho to your own 64 sink delegate. Set this to null to go back to Scriptlike's default 65 of "echo to stdout" again. 66 67 Note, setting this does not automatically enable echoing. You still need to 68 set either scriptlikeEcho or scriptlikeDryRun to true. 69 +/ 70 void delegate(string) scriptlikeCustomEcho; 71 72 /// Indicates a command returned a non-zero errorlevel. 73 class ErrorLevelException : Exception 74 { 75 int errorLevel; 76 string command; 77 78 this(int errorLevel, string command, string file=__FILE__, size_t line=__LINE__) 79 { 80 this.errorLevel = errorLevel; 81 this.command = command; 82 auto msg = text("Command exited with error level ", errorLevel, ": ", command); 83 super(msg, file, line); 84 } 85 } 86 87 /// Represents a file extension. 88 struct ExtT(C = char) if( is(C==char) || is(C==wchar) || is(C==dchar) ) 89 { 90 private immutable(C)[] str; 91 92 /// Main constructor. 93 this(immutable(C)[] extension = null) 94 { 95 this.str = extension; 96 } 97 98 /// Convert to string. 99 string toString() 100 { 101 return str.to!string(); 102 } 103 104 /// Convert to string, wstring or dstring, depending on the type of Ext. 105 immutable(C)[] toRawString() 106 { 107 return str; 108 } 109 110 /// Compare using OS-specific case-sensitivity rules. If you want to force 111 /// case-sensitive or case-insensistive, then call filenameCmp instead. 112 int opCmp(ref const ExtT!C other) const 113 { 114 return filenameCmp(this.str, other.str); 115 } 116 117 ///ditto 118 int opCmp(ExtT!C other) const 119 { 120 return filenameCmp(this.str, other.str); 121 } 122 123 ///ditto 124 int opCmp(string other) const 125 { 126 return filenameCmp(this.str, other); 127 } 128 129 /// Compare using OS-specific case-sensitivity rules. If you want to force 130 /// case-sensitive or case-insensistive, then call filenameCmp instead. 131 int opEquals(ref const ExtT!C other) const 132 { 133 return opCmp(other) == 0; 134 } 135 136 ///ditto 137 int opEquals(ExtT!C other) const 138 { 139 return opCmp(other) == 0; 140 } 141 142 ///ditto 143 int opEquals(string other) const 144 { 145 return opCmp(other) == 0; 146 } 147 148 /// Convert to bool 149 T opCast(T)() if(is(T==bool)) 150 { 151 return !!str; 152 } 153 } 154 alias Ext = ExtT!char; ///ditto 155 alias WExt = ExtT!wchar; ///ditto 156 alias DExt = ExtT!dchar; ///ditto 157 158 /// Represents a filesystem path. The path is always kept normalized 159 /// automatically (as performed by buildNormalizedPathFixed). 160 /// 161 /// wchar and dchar versions not yet supported, blocked by DMD issue #12112 162 struct PathT(C = char) if( is(C==char) /+|| is(C==wchar) || is(C==dchar)+/ ) 163 { 164 private immutable(C)[] str = "."; 165 166 /// Main constructor. 167 this()(const(C)[] path = ".") @safe pure nothrow 168 { 169 this.str = buildNormalizedPathFixed(path); 170 } 171 172 /++ 173 Convert from one type of Path to another. 174 Example: 175 auto pathStr = Path("foo"); 176 auto pathWStr = PathT!wchar(pathStr); 177 auto pathDStr = PathT!dchar(pathStr); 178 pathStr = Path(pathWStr); 179 pathStr = Path(pathDStr); 180 +/ 181 this(T)(T[] path = ".") if(isSomeChar!T) 182 { 183 this.str = to!(immutable(C)[])( buildNormalizedPathFixed(path.to!string()) ); 184 } 185 186 @trusted pure nothrow invariant() 187 { 188 assert(str == buildNormalizedPathFixed(str)); 189 } 190 191 /// Convert to string, quoting or escaping spaces if necessary. 192 string toString() 193 { 194 return escapeShellArg(str); 195 } 196 197 /// Convert to string, wstring or dstring, depending on the type of Path. 198 /// Does NOT do any escaping, even if path contains spaces. 199 immutable(C)[] toRawString() 200 { 201 return str; 202 } 203 204 /// Concatenates two paths, with a directory separator in between. 205 PathT!C opBinary(string op)(PathT!C rhs) if(op=="~") 206 { 207 PathT!C newPath; 208 newPath.str = buildNormalizedPathFixed(this.str, rhs.str); 209 return newPath; 210 } 211 212 ///ditto 213 PathT!C opBinary(string op)(const(C)[] rhs) if(op=="~") 214 { 215 PathT!C newPath; 216 newPath.str = buildNormalizedPathFixed(this.str, rhs); 217 return newPath; 218 } 219 220 ///ditto 221 PathT!C opBinaryRight(string op)(const(C)[] lhs) if(op=="~") 222 { 223 PathT!C newPath; 224 newPath.str = buildNormalizedPathFixed(lhs, this.str); 225 return newPath; 226 } 227 228 /// Appends an extension to a path. Naturally, a directory separator 229 /// is NOT inserted in between. 230 PathT!C opBinary(string op)(ExtT!C rhs) if(op=="~") 231 { 232 PathT!C newPath; 233 newPath.str = this.str.setExtension(rhs.str); 234 return newPath; 235 } 236 237 /// Appends a path to this one, with a directory separator in between. 238 PathT!C opOpAssign(string op)(PathT!C rhs) if(op=="~") 239 { 240 str = buildNormalizedPathFixed(str, rhs.str); 241 return this; 242 } 243 244 ///ditto 245 PathT!C opOpAssign(string op)(const(C)[] rhs) if(op=="~") 246 { 247 str = buildNormalizedPathFixed(str, rhs); 248 return this; 249 } 250 251 /// Appends an extension to this path. Naturally, a directory separator 252 /// is NOT inserted in between. 253 PathT!C opOpAssign(string op)(ExtT!C rhs) if(op=="~") 254 { 255 str = str.setExtension(rhs.str); 256 return this; 257 } 258 259 /// Compare using OS-specific case-sensitivity rules. If you want to force 260 /// case-sensitive or case-insensistive, then call filenameCmp instead. 261 int opCmp(ref const PathT!C other) const 262 { 263 return filenameCmp(this.str, other.str); 264 } 265 266 ///ditto 267 int opCmp(PathT!C other) const 268 { 269 return filenameCmp(this.str, other.str); 270 } 271 272 ///ditto 273 int opCmp(string other) const 274 { 275 return filenameCmp(this.str, other); 276 } 277 278 /// Compare using OS-specific case-sensitivity rules. If you want to force 279 /// case-sensitive or case-insensistive, then call filenameCmp instead. 280 int opEquals(ref const PathT!C other) const 281 { 282 return opCmp(other) == 0; 283 } 284 285 ///ditto 286 int opEquals(PathT!C other) const 287 { 288 return opCmp(other) == 0; 289 } 290 291 ///ditto 292 int opEquals(string other) const 293 { 294 return opCmp(other) == 0; 295 } 296 297 /// Convert to bool 298 T opCast(T)() if(is(T==bool)) 299 { 300 return !!str; 301 } 302 303 /// Returns the parent path, according to std.path.dirName. 304 @property PathT!C up() 305 { 306 return this.dirName(); 307 } 308 309 /// Is this path equal to empty string? 310 @property bool empty() 311 { 312 return str == ""; 313 } 314 } 315 alias Path = PathT!char; ///ditto 316 //alias WPath = PathT!wchar; ///ditto 317 //alias DPath = PathT!dchar; ///ditto 318 319 /// Convenience aliases 320 alias extOf = extension; 321 alias stripExt = stripExtension; ///ditto 322 alias setExt = setExtension; ///ditto 323 alias defaultExt = defaultExtension; ///ditto 324 325 /// Checks if the path exists as a directory. 326 /// 327 /// This is like std.file.isDir, but returns false instead of throwing if the 328 /// path doesn't exist. 329 bool existsAsDir()(in char[] path) @trusted 330 { 331 return exists(path) && isDir(path); 332 } 333 ///ditto 334 bool existsAsDir(C)(in PathT!C path) @trusted if(isSomeChar!C) 335 { 336 return existsAsDir(path.str.to!string()); 337 } 338 339 /// Checks if the path exists as a file. 340 /// 341 /// This is like std.file.isFile, but returns false instead of throwing if the 342 /// path doesn't exist. 343 bool existsAsFile()(in char[] path) @trusted 344 { 345 return exists(path) && isFile(path); 346 } 347 ///ditto 348 bool existsAsFile(C)(in PathT!C path) @trusted if(isSomeChar!C) 349 { 350 return existsAsFile(path.str.to!string()); 351 } 352 353 /// Checks if the path exists as a symlink. 354 /// 355 /// This is like std.file.isSymlink, but returns false instead of throwing if the 356 /// path doesn't exist. 357 bool existsAsSymlink()(in char[] path) @trusted 358 { 359 return exists(path) && isSymlink(path); 360 } 361 ///ditto 362 bool existsAsSymlink(C)(in PathT!C path) @trusted if(isSomeChar!C) 363 { 364 return existsAsSymlink(path.str.to!string()); 365 } 366 367 /// Like buildNormalizedPath, but if the result is the current directory, 368 /// this returns "." instead of "". However, if all the inputs are "", or there 369 /// are no inputs, this still returns "" just like buildNormalizedPath. 370 immutable(C)[] buildNormalizedPathFixed(C)(const(C[])[] paths...) 371 @trusted pure nothrow 372 if(isSomeChar!C) 373 { 374 if(all!`a is null`(paths)) 375 return null; 376 377 if(all!`a==""`(paths)) 378 return ""; 379 380 auto result = buildNormalizedPath(paths); 381 return result==""? "." : result; 382 } 383 384 /// Properly escape arguments containing spaces for the command shell, if necessary. 385 string escapeShellArg(T)(T str) if(isSomeString!T) 386 { 387 string s = str.to!string(); 388 389 if(str.canFind(' ')) 390 { 391 version(Windows) 392 return escapeWindowsArgument(s); 393 else version(Posix) 394 return escapeShellFileName(s); 395 else 396 static assert(0, "This platform not supported."); 397 } 398 else 399 return s; 400 } 401 402 private void echoCommand(lazy string command) 403 { 404 if(scriptlikeEcho || scriptlikeDryRun) 405 { 406 if(scriptlikeCustomEcho) 407 scriptlikeCustomEcho(command); 408 else 409 writeln(command); 410 } 411 } 412 413 /++ 414 Runs a command, through the system's command shell interpreter, 415 in typical shell-script style: Synchronously, with the command's 416 stdout/in/err automatically forwarded through your 417 program's stdout/in/err. 418 419 Optionally takes a working directory to run the command from. 420 421 The command is echoed if scriptlikeEcho is true. 422 423 ErrorLevelException is thrown if the process returns a non-zero error level. 424 If you want to handle the error level yourself, use tryRun instead of run. 425 426 Example: 427 --------------------- 428 Args cmd; 429 cmd ~= Path("some tool"); 430 cmd ~= "-o"; 431 cmd ~= Path(`dir/out file.txt`); 432 cmd ~= ["--abc", "--def", "-g"]; 433 Path("some working dir").run(cmd.data); 434 --------------------- 435 +/ 436 void run()(string command) 437 { 438 auto errorLevel = tryRun(command); 439 if(errorLevel != 0) 440 throw new ErrorLevelException(errorLevel, command); 441 } 442 443 ///ditto 444 void run(C)(PathT!C workingDirectory, string command) 445 { 446 auto saveDir = getcwd(); 447 workingDirectory.chdir(); 448 scope(exit) saveDir.chdir(); 449 450 run(command); 451 } 452 453 /++ 454 Runs a command, through the system's command shell interpreter, 455 in typical shell-script style: Synchronously, with the command's 456 stdout/in/err automatically forwarded through your 457 program's stdout/in/err. 458 459 Optionally takes a working directory to run the command from. 460 461 The command is echoed if scriptlikeEcho is true. 462 463 Returns: The error level the process exited with. 464 465 Example: 466 --------------------- 467 Args cmd; 468 cmd ~= Path("some tool"); 469 cmd ~= "-o"; 470 cmd ~= Path(`dir/out file.txt`); 471 cmd ~= ["--abc", "--def", "-g"]; 472 auto errLevel = Path("some working dir").run(cmd.data); 473 --------------------- 474 +/ 475 int tryRun()(string command) 476 { 477 echoCommand(command); 478 479 if(scriptlikeDryRun) 480 return 0; 481 else 482 return system(command); 483 } 484 485 ///ditto 486 int tryRun(C)(PathT!C workingDirectory, string command) 487 { 488 auto saveDir = getcwd(); 489 workingDirectory.chdir(); 490 scope(exit) saveDir.chdir(); 491 492 return tryRun(command); 493 } 494 495 /// Backwards-compatibility alias. runShell may become depricated in the 496 /// future, so you should use tryRun or run insetad. 497 alias runShell = tryRun; 498 499 // -- Wrappers for std.path -------------------- 500 501 /// Part of workaround for DMD Issue #12111 502 alias dirSeparator = std.path.dirSeparator; 503 alias pathSeparator = std.path.pathSeparator; ///ditto 504 alias isDirSeparator = std.path.isDirSeparator; ///ditto 505 alias CaseSensitive = std.path.CaseSensitive; ///ditto 506 alias osDefaultCaseSensitivity = std.path.osDefaultCaseSensitivity; ///ditto 507 alias buildPath = std.path.buildPath; ///ditto 508 alias buildNormalizedPath = std.path.buildNormalizedPath; ///ditto 509 510 /// Just like std.path.baseName, but operates on Path. 511 PathT!C baseName(C)(PathT!C path) 512 @trusted pure 513 { 514 return PathT!C( path.str.baseName() ); 515 } 516 517 ///ditto 518 PathT!C baseName(CaseSensitive cs = CaseSensitive.osDefault, C, C1) 519 (PathT!C path, in C1[] suffix) 520 @safe pure 521 if(isSomeChar!C1) 522 { 523 return PathT!C( path.str.baseName!cs(suffix) ); 524 } 525 526 /// Part of workaround for DMD Issue #12111 527 inout(C)[] baseName(C)(inout(C)[] path) 528 @trusted pure 529 if (isSomeChar!C) 530 { 531 return std.path.baseName(path); 532 } 533 534 ///ditto 535 inout(C)[] baseName(CaseSensitive cs = CaseSensitive.osDefault, C, C1) 536 (inout(C)[] path, in C1[] suffix) 537 @safe pure 538 if (isSomeChar!C && isSomeChar!C1) 539 { 540 return std.path.baseName(path, suffix); 541 } 542 543 /// Just like std.path.dirName, but operates on Path. 544 PathT!C dirName(C)(PathT!C path) if(isSomeChar!C) 545 { 546 return PathT!C( path.str.dirName() ); 547 } 548 549 /// Part of workaround for DMD Issue #12111 550 C[] dirName(C)(C[] path) 551 if (isSomeChar!C) 552 { 553 return std.path.dirName(path); 554 } 555 556 /// Just like std.path.rootName, but operates on Path. 557 PathT!C rootName(C)(PathT!C path) @safe pure nothrow 558 { 559 return PathT!C( path.str.rootName() ); 560 } 561 562 /// Part of workaround for DMD Issue #12111 563 inout(C)[] rootName(C)(inout(C)[] path) @safe pure nothrow if (isSomeChar!C) 564 { 565 return std.path.rootName(path); 566 } 567 568 /// Just like std.path.driveName, but operates on Path. 569 PathT!C driveName(C)(PathT!C path) @safe pure nothrow 570 { 571 return PathT!C( path.str.driveName() ); 572 } 573 574 /// Part of workaround for DMD Issue #12111 575 inout(C)[] driveName(C)(inout(C)[] path) @safe pure nothrow 576 if (isSomeChar!C) 577 { 578 return std.path.driveName(path); 579 } 580 581 /// Just like std.path.stripDrive, but operates on Path. 582 PathT!C stripDrive(C)(PathT!C path) @safe pure nothrow 583 { 584 return PathT!C( path.str.stripDrive() ); 585 } 586 587 /// Part of workaround for DMD Issue #12111 588 inout(C)[] stripDrive(C)(inout(C)[] path) @safe pure nothrow if (isSomeChar!C) 589 { 590 return std.path.stripDrive(path); 591 } 592 593 /// Just like std.path.extension, but takes a Path and returns an Ext. 594 ExtT!C extension(C)(in PathT!C path) @safe pure nothrow 595 { 596 return ExtT!C( path.str.extension() ); 597 } 598 599 /// Part of workaround for DMD Issue #12111 600 inout(C)[] extension(C)(inout(C)[] path) @safe pure nothrow if (isSomeChar!C) 601 { 602 return std.path.extension(path); 603 } 604 605 /// Just like std.path.stripExtension, but operates on Path. 606 PathT!C stripExtension(C)(PathT!C path) @safe pure nothrow 607 { 608 return PathT!C( path.str.stripExtension() ); 609 } 610 611 /// Part of workaround for DMD Issue #12111 612 inout(C)[] stripExtension(C)(inout(C)[] path) @safe pure nothrow 613 if (isSomeChar!C) 614 { 615 return std.path.stripExtension(path); 616 } 617 618 /// Just like std.path.setExtension, but operates on Path. 619 PathT!C setExtension(C, C2)(PathT!C path, const(C2)[] ext) 620 @trusted pure nothrow 621 if(is(C == Unqual!C2)) 622 { 623 return PathT!C( path.str.setExtension(ext) ); 624 } 625 626 ///ditto 627 PathT!C setExtension(C)(PathT!C path, ExtT!C ext) 628 @trusted pure nothrow 629 { 630 return path.setExtension(ext.toString()); 631 } 632 633 /// Part of workaround for DMD Issue #12111 634 immutable(Unqual!C1)[] setExtension(C1, C2)(in C1[] path, in C2[] ext) 635 @trusted pure nothrow 636 if (isSomeChar!C1 && !is(C1 == immutable) && is(Unqual!C1 == Unqual!C2)) 637 { 638 return std.path.setExtension(path, ext); 639 } 640 641 /// Part of workaround for DMD Issue #12111 642 immutable(C1)[] setExtension(C1, C2)(immutable(C1)[] path, const(C2)[] ext) 643 @trusted pure nothrow 644 if (isSomeChar!C1 && is(Unqual!C1 == Unqual!C2)) 645 { 646 return std.path.setExtension(path, ext); 647 } 648 649 /// Just like std.path.defaultExtension, but operates on Path and optionally Ext. 650 PathT!C defaultExtension(C, C2)(PathT!C path, in C2[] ext) 651 @trusted pure 652 if(is(C == Unqual!C2)) 653 { 654 return PathT!C( path.str.defaultExtension(ext) ); 655 } 656 657 ///ditto 658 PathT!C defaultExtension(C)(PathT!C path, ExtT!C ext) 659 @trusted pure 660 { 661 return path.defaultExtension(ext.toString()); 662 } 663 664 /// Part of workaround for DMD Issue #12111 665 immutable(Unqual!C1)[] defaultExtension(C1, C2)(in C1[] path, in C2[] ext) 666 @trusted pure 667 if (isSomeChar!C1 && is(Unqual!C1 == Unqual!C2)) 668 { 669 return std.path.defaultExtension(path, ext); 670 } 671 672 /// Just like std.path.pathSplitter. Note this returns a range of strings, 673 /// not a range of Path. 674 auto pathSplitter(C)(PathT!C path) @safe pure nothrow 675 { 676 return pathSplitter(path.str); 677 } 678 679 /// Part of workaround for DMD Issue #12111 680 ReturnType!(std.path.pathSplitter!C) pathSplitter(C)(const(C)[] path) @safe pure nothrow 681 if (isSomeChar!C) 682 { 683 return std.path.pathSplitter(path); 684 } 685 686 /// Just like std.path.isRooted, but operates on Path. 687 bool isRooted(C)(in PathT!C path) @safe pure nothrow 688 { 689 return path.str.isRooted(); 690 } 691 692 /// Part of workaround for DMD Issue #12111 693 bool isRooted(C)(in C[] path) @safe pure nothrow if (isSomeChar!C) 694 { 695 return std.path.isRooted(path); 696 } 697 698 /// Just like std.path.isAbsolute, but operates on Path. 699 bool isAbsolute(C)(in PathT!C path) @safe pure nothrow 700 { 701 return path.str.isAbsolute(); 702 } 703 704 /// Part of workaround for DMD Issue #12111 705 bool isAbsolute(C)(in C[] path) @safe pure nothrow 706 if (isSomeChar!C) 707 { 708 return std.path.isAbsolute(path); 709 } 710 711 /// Just like std.path.absolutePath, but operates on Path. 712 PathT!C absolutePath(C)(PathT!C path, lazy string base = getcwd()) 713 @safe pure 714 { 715 return PathT!C( path.str.absolutePath(base) ); 716 } 717 718 ///ditto 719 PathT!C absolutePath(C)(PathT!C path, PathT!C base) 720 @safe pure 721 { 722 return PathT!C( path.str.absolutePath(base.str.to!string()) ); 723 } 724 725 /// Part of workaround for DMD Issue #12111 726 string absolutePath(string path, lazy string base = getcwd()) 727 @safe pure 728 { 729 return std.path.absolutePath(path, base); 730 } 731 732 /// Just like std.path.relativePath, but operates on Path. 733 PathT!C relativePath(CaseSensitive cs = CaseSensitive.osDefault, C) 734 (PathT!C path, lazy string base = getcwd()) 735 { 736 return PathT!C( path.str.relativePath!cs(base) ); 737 } 738 739 ///ditto 740 PathT!C relativePath(CaseSensitive cs = CaseSensitive.osDefault, C) 741 (PathT!C path, PathT!C base) 742 { 743 return PathT!C( path.str.relativePath!cs(base.str.to!string()) ); 744 } 745 746 /// Part of workaround for DMD Issue #12111 747 string relativePath(CaseSensitive cs = CaseSensitive.osDefault) 748 (string path, lazy string base = getcwd()) 749 { 750 return std.path.relativePath(path, base); 751 } 752 753 /// Part of workaround for DMD Issue #12111 754 int filenameCharCmp(CaseSensitive cs = CaseSensitive.osDefault)(dchar a, dchar b) 755 @safe pure nothrow 756 { 757 return std.path.filenameCharCmp(a, b); 758 } 759 760 /// Just like std.path.filenameCmp, but operates on Path. 761 int filenameCmp(CaseSensitive cs = CaseSensitive.osDefault, C, C2) 762 (PathT!C path, PathT!C2 filename2) 763 @safe pure 764 { 765 return path.str.filenameCmp(filename2.str); 766 } 767 768 ///ditto 769 int filenameCmp(CaseSensitive cs = CaseSensitive.osDefault, C, C2) 770 (PathT!C path, const(C2)[] filename2) 771 @safe pure 772 if(isSomeChar!C2) 773 { 774 return path.str.filenameCmp(filename2); 775 } 776 777 ///ditto 778 int filenameCmp(CaseSensitive cs = CaseSensitive.osDefault, C, C2) 779 (const(C)[] path, PathT!C2[] filename2) 780 @safe pure 781 if(isSomeChar!C) 782 { 783 return path.filenameCmp(filename2.str); 784 } 785 786 /// Part of workaround for DMD Issue #12111 787 int filenameCmp(CaseSensitive cs = CaseSensitive.osDefault, C1, C2) 788 (const(C1)[] filename1, const(C2)[] filename2) 789 @safe pure 790 if (isSomeChar!C1 && isSomeChar!C2) 791 { 792 return std.path.filenameCmp(filename1, filename2); 793 } 794 795 /// Just like std.path.globMatch, but operates on Path. 796 bool globMatch(CaseSensitive cs = CaseSensitive.osDefault, C) 797 (PathT!C path, const(C)[] pattern) 798 @safe pure nothrow 799 { 800 return path.str.globMatch!cs(pattern); 801 } 802 803 /// Part of workaround for DMD Issue #12111 804 bool globMatch(CaseSensitive cs = CaseSensitive.osDefault, C) 805 (const(C)[] path, const(C)[] pattern) 806 @safe pure nothrow 807 if (isSomeChar!C) 808 { 809 return std.path.globMatch(path, pattern); 810 } 811 812 /// Just like std.path.isValidFilename, but operates on Path. 813 bool isValidFilename(C)(in PathT!C path) @safe pure nothrow 814 { 815 return path.str.isValidFilename(); 816 } 817 818 /// Part of workaround for DMD Issue #12111 819 bool isValidFilename(C)(in C[] filename) @safe pure nothrow if (isSomeChar!C) 820 { 821 return std.path.isValidFilename(filename); 822 } 823 824 /// Just like std.path.isValidPath, but operates on Path. 825 bool isValidPath(C)(in PathT!C path) @safe pure nothrow 826 { 827 return path.str.isValidPath(); 828 } 829 830 /// Part of workaround for DMD Issue #12111 831 bool isValidPath(C)(in C[] path) @safe pure nothrow if (isSomeChar!C) 832 { 833 return std.path.isValidPath(path); 834 } 835 836 /// Just like std.path.expandTilde, but operates on Path. 837 PathT!C expandTilde(C)(PathT!C path) 838 { 839 static if( is(C == char) ) 840 return PathT!C( path.str.expandTilde() ); 841 else 842 return PathT!C( path.to!string().expandTilde().to!(C[])() ); 843 } 844 845 /// Part of workaround for DMD Issue #12111 846 string expandTilde(string inputPath) 847 { 848 return std.path.expandTilde(inputPath); 849 } 850 851 // -- Wrappers for std.file -------------------- 852 853 /// Part of workaround for DMD Issue #12111 854 alias FileException = std.file.FileException; 855 alias SpanMode = std.file.SpanMode; 856 alias attrIsDir = std.file.attrIsDir; 857 alias attrIsFile = std.file.attrIsFile; 858 alias attrIsSymlink = std.file.attrIsSymlink; 859 alias getcwd = std.file.getcwd; 860 alias thisExePath = std.file.thisExePath; 861 alias tempDir = std.file.tempDir; 862 863 /// Just like std.file.read, but takes a Path. 864 void[] read(C)(in PathT!C name, size_t upTo = size_t.max) if(isSomeChar!C) 865 { 866 return read(name.str.to!string(), upTo); 867 } 868 869 /// Part of workaround for DMD Issue #12111 870 void[] read(in char[] name, size_t upTo = size_t.max) 871 { 872 return std.file.read(name, upTo); 873 } 874 875 /// Just like std.file.readText, but takes a Path. 876 template readText(S = string) 877 { 878 S readText(C)(in PathT!C name) if(isSomeChar!C) 879 { 880 return std.file.readText(name.str.to!string()); 881 } 882 } 883 884 /// Part of workaround for DMD Issue #12111 885 S readText(S = string)(in char[] name) 886 { 887 return std.file.readText(name); 888 } 889 890 /// Just like std.file.write, but optionally takes a Path, 891 /// and obeys scriptlikeEcho and scriptlikeDryRun. 892 void write(C)(in PathT!C name, const void[] buffer) if(isSomeChar!C) 893 { 894 write(name.str.to!string(), buffer); 895 } 896 897 ///ditto 898 void write(in char[] name, const void[] buffer) 899 { 900 echoCommand(text("Write ", name)); 901 902 if(!scriptlikeDryRun) 903 std.file.write(name, buffer); 904 } 905 906 /// Just like std.file.append, but optionally takes a Path, 907 /// and obeys scriptlikeEcho and scriptlikeDryRun. 908 void append(C)(in PathT!C name, in void[] buffer) if(isSomeChar!C) 909 { 910 append(name.str.to!string(), buffer); 911 } 912 913 ///ditto 914 void append(in char[] name, in void[] buffer) 915 { 916 echoCommand(text("Append ", name)); 917 918 if(!scriptlikeDryRun) 919 std.file.append(name, buffer); 920 } 921 922 /// Just like std.file.rename, but optionally takes Path, 923 /// and obeys scriptlikeEcho and scriptlikeDryRun. 924 void rename(C)(in PathT!C from, in PathT!C to) if(isSomeChar!C) 925 { 926 rename(from.str.to!string(), to.str.to!string()); 927 } 928 929 ///ditto 930 void rename(C)(in char[] from, in PathT!C to) if(isSomeChar!C) 931 { 932 rename(from, to.str.to!string()); 933 } 934 935 ///ditto 936 void rename(C)(in PathT!C from, in char[] to) if(isSomeChar!C) 937 { 938 rename(from.str.to!string(), to); 939 } 940 941 ///ditto 942 void rename(in char[] from, in char[] to) 943 { 944 echoCommand("rename: "~from.escapeShellArg()~" -> "~to.escapeShellArg()); 945 946 if(!scriptlikeDryRun) 947 std.file.rename(from, to); 948 } 949 950 /// If 'from' exists, then rename. Otherwise do nothing. 951 /// Obeys scriptlikeEcho and scriptlikeDryRun. 952 /// Returns: Success? 953 bool tryRename(T1, T2)(T1 from, T2 to) 954 { 955 if(from.exists()) 956 { 957 rename(from, to); 958 return true; 959 } 960 961 return false; 962 } 963 964 /// Just like std.file.remove, but optionally takes a Path, 965 /// and obeys scriptlikeEcho and scriptlikeDryRun. 966 void remove(C)(in PathT!C name) if(isSomeChar!C) 967 { 968 remove(name.str.to!string()); 969 } 970 971 ///ditto 972 void remove(in char[] name) 973 { 974 echoCommand("remove: "~name.escapeShellArg()); 975 976 if(!scriptlikeDryRun) 977 std.file.remove(name); 978 } 979 980 /// If 'name' exists, then remove. Otherwise do nothing. 981 /// Obeys scriptlikeEcho and scriptlikeDryRun. 982 /// Returns: Success? 983 bool tryRemove(T)(T name) 984 { 985 if(name.exists()) 986 { 987 remove(name); 988 return true; 989 } 990 991 return false; 992 } 993 994 /// Just like std.file.getSize, but takes a Path. 995 ulong getSize(C)(in PathT!C name) if(isSomeChar!C) 996 { 997 return getSize(name.str.to!string()); 998 } 999 1000 /// Part of workaround for DMD Issue #12111 1001 ulong getSize(in char[] name) 1002 { 1003 return std.file.getSize(name); 1004 } 1005 1006 /// Just like std.file.getTimes, but takes a Path. 1007 void getTimes(C)(in PathT!C name, 1008 out SysTime accessTime, 1009 out SysTime modificationTime) if(isSomeChar!C) 1010 { 1011 getTimes(name.str.to!string(), accessTime, modificationTime); 1012 } 1013 1014 /// Part of workaround for DMD Issue #12111 1015 void getTimes(in char[] name, 1016 out SysTime accessTime, 1017 out SysTime modificationTime) 1018 { 1019 std.file.getTimes(name, accessTime, modificationTime); 1020 } 1021 1022 version(ddoc_scriptlike_d) 1023 { 1024 /// Windows-only. Just like std.file.getTimesWin, but takes a Path. 1025 void getTimesWin(C)(in PathT!C name, 1026 out SysTime fileCreationTime, 1027 out SysTime fileAccessTime, 1028 out SysTime fileModificationTime) if(isSomeChar!C); 1029 } 1030 else version(Windows) void getTimesWin(C)(in PathT!C name, 1031 out SysTime fileCreationTime, 1032 out SysTime fileAccessTime, 1033 out SysTime fileModificationTime) if(isSomeChar!C) 1034 { 1035 getTimesWin(name.str.to!string(), fileCreationTime, fileAccessTime, fileModificationTime); 1036 } 1037 1038 version(ddoc_scriptlike_d) 1039 { 1040 /// Windows-only. Part of workaround for DMD Issue #12111 1041 void getTimesWin(in char[] name, 1042 out SysTime fileCreationTime, 1043 out SysTime fileAccessTime, 1044 out SysTime fileModificationTime); 1045 } 1046 else version(Windows) void getTimesWin(in char[] name, 1047 out SysTime fileCreationTime, 1048 out SysTime fileAccessTime, 1049 out SysTime fileModificationTime) 1050 { 1051 std.file.getTimesWin(name, fileCreationTime, fileAccessTime, fileModificationTime); 1052 } 1053 1054 /// Just like std.file.setTimes, but optionally takes a Path, 1055 /// and obeys scriptlikeEcho and scriptlikeDryRun. 1056 void setTimes(C)(in PathT!C name, 1057 SysTime accessTime, 1058 SysTime modificationTime) if(isSomeChar!C) 1059 { 1060 setTimes(name.str.to!string(), accessTime, modificationTime); 1061 } 1062 1063 ///ditto 1064 void setTimes(in char[] name, 1065 SysTime accessTime, 1066 SysTime modificationTime) 1067 { 1068 echoCommand(text( 1069 "setTimes: ", name.escapeShellArg(), 1070 " Accessed ", accessTime, "; Modified ", modificationTime 1071 )); 1072 1073 if(!scriptlikeDryRun) 1074 std.file.setTimes(name, accessTime, modificationTime); 1075 } 1076 1077 /// Just like std.file.timeLastModified, but takes a Path. 1078 SysTime timeLastModified(C)(in PathT!C name) if(isSomeChar!C) 1079 { 1080 return timeLastModified(name.str.to!string()); 1081 } 1082 1083 /// Just like std.file.timeLastModified, but takes a Path. 1084 SysTime timeLastModified(C)(in PathT!C name, SysTime returnIfMissing) if(isSomeChar!C) 1085 { 1086 return timeLastModified(name.str.to!string(), returnIfMissing); 1087 } 1088 1089 /// Part of workaround for DMD Issue #12111 1090 SysTime timeLastModified(in char[] name) 1091 { 1092 return std.file.timeLastModified(name); 1093 } 1094 1095 ///ditto 1096 SysTime timeLastModified(in char[] name, SysTime returnIfMissing) 1097 { 1098 return std.file.timeLastModified(name, returnIfMissing); 1099 } 1100 1101 /// Just like std.file.exists, but takes a Path. 1102 bool exists(C)(in PathT!C name) @trusted if(isSomeChar!C) 1103 { 1104 return exists(name.str.to!string()); 1105 } 1106 1107 /// Part of workaround for DMD Issue #12111 1108 bool exists(in char[] name) @trusted 1109 { 1110 return std.file.exists(name); 1111 } 1112 1113 /// Just like std.file.getAttributes, but takes a Path. 1114 uint getAttributes(C)(in PathT!C name) if(isSomeChar!C) 1115 { 1116 return getAttributes(name.str.to!string()); 1117 } 1118 1119 /// Part of workaround for DMD Issue #12111 1120 uint getAttributes(in char[] name) 1121 { 1122 return std.file.getAttributes(name); 1123 } 1124 1125 /// Just like std.file.getLinkAttributes, but takes a Path. 1126 uint getLinkAttributes(C)(in PathT!C name) if(isSomeChar!C) 1127 { 1128 return getLinkAttributes(name.str.to!string()); 1129 } 1130 1131 /// Part of workaround for DMD Issue #12111 1132 uint getLinkAttributes(in char[] name) 1133 { 1134 return std.file.getLinkAttributes(name); 1135 } 1136 1137 /// Just like std.file.isDir, but takes a Path. 1138 @property bool isDir(C)(in PathT!C name) if(isSomeChar!C) 1139 { 1140 return isDir(name.str.to!string()); 1141 } 1142 1143 /// Part of workaround for DMD Issue #12111 1144 @property bool isDir(in char[] name) 1145 { 1146 return std.file.isDir(name); 1147 } 1148 1149 /// Just like std.file.isFile, but takes a Path. 1150 @property bool isFile(C)(in PathT!C name) if(isSomeChar!C) 1151 { 1152 return isFile(name.str.to!string()); 1153 } 1154 1155 /// Part of workaround for DMD Issue #12111 1156 @property bool isFile(in char[] name) 1157 { 1158 return std.file.isFile(name); 1159 } 1160 1161 /// Just like std.file.isSymlink, but takes a Path. 1162 @property bool isSymlink(C)(PathT!C name) if(isSomeChar!C) 1163 { 1164 return isSymlink(name.str.to!string()); 1165 } 1166 1167 /// Part of workaround for DMD Issue #12111 1168 @property bool isSymlink(C)(const(C)[] name) 1169 { 1170 return std.file.isSymlink(name); 1171 } 1172 1173 /// Just like std.file.chdir, but takes a Path, and echoes if scriptlikeEcho is true. 1174 void chdir(C)(in PathT!C pathname) if(isSomeChar!C) 1175 { 1176 chdir(pathname.str.to!string()); 1177 } 1178 1179 /// Just like std.file.chdir, but echoes if scriptlikeEcho is true. 1180 void chdir(in char[] pathname) 1181 { 1182 echoCommand("chdir: "~pathname.escapeShellArg()); 1183 std.file.chdir(pathname); 1184 } 1185 1186 /// Just like std.file.mkdir, but optionally takes a Path, 1187 /// and obeys scriptlikeEcho and scriptlikeDryRun. 1188 void mkdir(C)(in PathT!C pathname) if(isSomeChar!C) 1189 { 1190 mkdir(pathname.str.to!string()); 1191 } 1192 1193 ///ditto 1194 void mkdir(in char[] pathname) 1195 { 1196 echoCommand("mkdir: "~pathname.escapeShellArg()); 1197 1198 if(!scriptlikeDryRun) 1199 std.file.mkdir(pathname); 1200 } 1201 1202 /// If 'name' doesn't already exist, then mkdir. Otherwise do nothing. 1203 /// Obeys scriptlikeEcho and scriptlikeDryRun. 1204 /// Returns: Success? 1205 bool tryMkdir(T)(T name) 1206 { 1207 if(!name.exists()) 1208 { 1209 mkdir(name); 1210 return true; 1211 } 1212 1213 return false; 1214 } 1215 1216 /// Just like std.file.mkdirRecurse, but optionally takes a Path, 1217 /// and obeys scriptlikeEcho and scriptlikeDryRun. 1218 void mkdirRecurse(C)(in PathT!C pathname) if(isSomeChar!C) 1219 { 1220 mkdirRecurse(pathname.str.to!string()); 1221 } 1222 1223 ///ditto 1224 void mkdirRecurse(in char[] pathname) 1225 { 1226 echoCommand("mkdirRecurse: "~pathname.escapeShellArg()); 1227 1228 if(!scriptlikeDryRun) 1229 std.file.mkdirRecurse(pathname); 1230 } 1231 1232 /// If 'name' doesn't already exist, then mkdirRecurse. Otherwise do nothing. 1233 /// Obeys scriptlikeEcho and scriptlikeDryRun. 1234 /// Returns: Success? 1235 bool tryMkdirRecurse(T)(T name) 1236 { 1237 if(!name.exists()) 1238 { 1239 mkdirRecurse(name); 1240 return true; 1241 } 1242 1243 return false; 1244 } 1245 1246 /// Just like std.file.rmdir, but optionally takes a Path, 1247 /// and obeys scriptlikeEcho and scriptlikeDryRun. 1248 void rmdir(C)(in PathT!C pathname) if(isSomeChar!C) 1249 { 1250 rmdir(pathname.str.to!string()); 1251 } 1252 1253 ///ditto 1254 void rmdir(in char[] pathname) 1255 { 1256 echoCommand("rmdir: "~pathname.escapeShellArg()); 1257 1258 if(!scriptlikeDryRun) 1259 std.file.rmdir(pathname); 1260 } 1261 1262 /// If 'name' exists, then rmdir. Otherwise do nothing. 1263 /// Obeys scriptlikeEcho and scriptlikeDryRun. 1264 /// Returns: Success? 1265 bool tryRmdir(T)(T name) 1266 { 1267 if(name.exists()) 1268 { 1269 rmdir(name); 1270 return true; 1271 } 1272 1273 return false; 1274 } 1275 1276 version(ddoc_scriptlike_d) 1277 { 1278 /// Posix-only. Just like std.file.symlink, but optionally takes Path, 1279 /// and obeys scriptlikeEcho and scriptlikeDryRun. 1280 void symlink(C1, C2)(PathT!C1 original, PathT!C2 link) if(isSomeChar!C1 && isSomeChar!C2); 1281 1282 ///ditto 1283 void symlink(C1, C2)(const(C1)[] original, PathT!C2 link) if(isSomeChar!C1 && isSomeChar!C2); 1284 1285 ///ditto 1286 void symlink(C1, C2)(PathT!C1 original, const(C2)[] link) if(isSomeChar!C1 && isSomeChar!C2); 1287 1288 ///ditto 1289 void symlink(C1, C2)(const(C1)[] original, const(C2)[] link); 1290 1291 /// Posix-only. If 'original' exists, then symlink. Otherwise do nothing. 1292 /// Obeys scriptlikeEcho and scriptlikeDryRun. 1293 /// Returns: Success? 1294 bool trySymlink(T1, T2)(T1 original, T2 link); 1295 1296 /// Posix-only. Just like std.file.readLink, but operates on Path. 1297 PathT!C readLink(C)(PathT!C link) if(isSomeChar!C); 1298 1299 /// Posix-only. Part of workaround for DMD Issue #12111 1300 string readLink(C)(const(C)[] link); 1301 } 1302 else version(Posix) 1303 { 1304 void symlink(C1, C2)(PathT!C1 original, PathT!C2 link) if(isSomeChar!C1 && isSomeChar!C2) 1305 { 1306 symlink(original.str.to!string(), link.str.to!string()); 1307 } 1308 1309 void symlink(C1, C2)(const(C1)[] original, PathT!C2 link) if(isSomeChar!C1 && isSomeChar!C2) 1310 { 1311 symlink(original, link.str.to!string()); 1312 } 1313 1314 void symlink(C1, C2)(PathT!C1 original, const(C2)[] link) if(isSomeChar!C1 && isSomeChar!C2) 1315 { 1316 symlink(original.str.to!string(), link); 1317 } 1318 1319 void symlink(C1, C2)(const(C1)[] original, const(C2)[] link) 1320 { 1321 echoCommand("symlink: [original] "~original.escapeShellArg()~" : [symlink] "~link.escapeShellArg()); 1322 1323 if(!scriptlikeDryRun) 1324 std.file.symlink(original, link); 1325 } 1326 1327 bool trySymlink(T1, T2)(T1 original, T2 link) 1328 { 1329 if(original.exists()) 1330 { 1331 symlink(original, link); 1332 return true; 1333 } 1334 1335 return false; 1336 } 1337 1338 PathT!C readLink(C)(PathT!C link) if(isSomeChar!C) 1339 { 1340 return PathT!C( readLink(link.str.to!string()) ); 1341 } 1342 1343 string readLink(C)(const(C)[] link) 1344 { 1345 return std.file.readLink(link); 1346 } 1347 } 1348 1349 /// Just like std.file.copy, but optionally takes Path, 1350 /// and obeys scriptlikeEcho and scriptlikeDryRun. 1351 void copy(C)(in PathT!C from, in PathT!C to) if(isSomeChar!C) 1352 { 1353 copy(from.str.to!string(), to.str.to!string()); 1354 } 1355 1356 ///ditto 1357 void copy(C)(in char[] from, in PathT!C to) if(isSomeChar!C) 1358 { 1359 copy(from, to.str.to!string()); 1360 } 1361 1362 ///ditto 1363 void copy(C)(in PathT!C from, in char[] to) if(isSomeChar!C) 1364 { 1365 copy(from.str.to!string(), to); 1366 } 1367 1368 ///ditto 1369 void copy(in char[] from, in char[] to) 1370 { 1371 echoCommand("copy: "~from.escapeShellArg()~" -> "~to.escapeShellArg()); 1372 1373 if(!scriptlikeDryRun) 1374 std.file.copy(from, to); 1375 } 1376 1377 /// If 'from' exists, then copy. Otherwise do nothing. 1378 /// Obeys scriptlikeEcho and scriptlikeDryRun. 1379 /// Returns: Success? 1380 bool tryCopy(T1, T2)(T1 from, T2 to) 1381 { 1382 if(from.exists()) 1383 { 1384 copy(from, to); 1385 return true; 1386 } 1387 1388 return false; 1389 } 1390 1391 /// Just like std.file.rmdirRecurse, but optionally takes a Path, 1392 /// and obeys scriptlikeEcho and scriptlikeDryRun. 1393 void rmdirRecurse(C)(in PathT!C pathname) if(isSomeChar!C) 1394 { 1395 rmdirRecurse(pathname.str.to!string()); 1396 } 1397 1398 ///ditto 1399 void rmdirRecurse(in char[] pathname) 1400 { 1401 echoCommand("rmdirRecurse: "~pathname.escapeShellArg()); 1402 1403 if(!scriptlikeDryRun) 1404 std.file.rmdirRecurse(pathname); 1405 } 1406 1407 /// If 'name' exists, then rmdirRecurse. Otherwise do nothing. 1408 /// Obeys scriptlikeEcho and scriptlikeDryRun. 1409 /// Returns: Success? 1410 bool tryRmdirRecurse(T)(T name) 1411 { 1412 if(name.exists()) 1413 { 1414 rmdirRecurse(name); 1415 return true; 1416 } 1417 1418 return false; 1419 } 1420 1421 /// Just like std.file.dirEntries, but takes a Path. 1422 auto dirEntries(C)(PathT!C path, SpanMode mode, bool followSymlink = true) if(isSomeChar!C) 1423 { 1424 return dirEntries(path.str.to!string(), mode, followSymlink); 1425 } 1426 1427 /// Just like std.file.dirEntries, but takes a Path. 1428 auto dirEntries(C)(PathT!C path, string pattern, SpanMode mode, 1429 bool followSymlink = true) if(isSomeChar!C) 1430 { 1431 return dirEntries(path.str.to!string(), pattern, mode, followSymlink); 1432 } 1433 1434 /// Part of workaround for DMD Issue #12111 1435 auto dirEntries(string path, SpanMode mode, bool followSymlink = true) 1436 { 1437 return std.file.dirEntries(path, mode, followSymlink); 1438 } 1439 1440 ///ditto 1441 auto dirEntries(string path, string pattern, SpanMode mode, 1442 bool followSymlink = true) 1443 { 1444 return std.file.dirEntries(path, pattern, mode, followSymlink); 1445 } 1446 1447 /// Just like std.file.slurp, but takes a Path. 1448 template slurp(Types...) 1449 { 1450 auto slurp(C)(PathT!C filename, in char[] format) if(isSomeChar!C) 1451 { 1452 return std.file.slurp!Types(filename.str.to!string(), format); 1453 } 1454 } 1455 1456 /// Part of workaround for DMD Issue #12111 1457 Select!(Types.length == 1, Types[0][], Tuple!(Types)[]) 1458 slurp(Types...)(string filename, in char[] format) 1459 { 1460 return std.file.slurp(filename, format); 1461 } 1462 1463 /++ 1464 Much like std.array.Appender!string, but specifically geared towards 1465 building a command string out of arguments. String and Path can both 1466 be appended. All elements added will automatically be escaped, 1467 and separated by spaces, as necessary. 1468 1469 Example: 1470 ------------------- 1471 Args args; 1472 args ~= Path(`some/big path/here/foobar`); 1473 args ~= "-A"; 1474 args ~= "--bcd"; 1475 args ~= "Hello World"; 1476 args ~= Path("file.ext"); 1477 1478 // On windows: 1479 assert(args.data == `"some\big path\here\foobar" -A --bcd "Hello World" file.ext`); 1480 // On linux: 1481 assert(args.data == `'some/big path/here/foobar' -A --bcd 'Hello World' file.ext`); 1482 ------------------- 1483 1484 wchar and dchar versions not yet supported, blocked by DMD issue #12112 1485 +/ 1486 struct ArgsT(C = char) if( is(C==char) /+|| is(C==wchar) || is(C==dchar)+/ ) 1487 { 1488 // Internal note: For every element the user adds to ArgsT, 1489 // *two* elements will be added to this internal buf: first a spacer 1490 // (normally a space, or an empty string in the case of the very first 1491 // element the user adds) and then the actual element the user added. 1492 private Appender!(immutable(C)[]) buf; 1493 private size_t _length = 0; 1494 1495 void reserve(size_t newCapacity) @safe pure nothrow 1496 { 1497 // "*2" to account for the spacers 1498 buf.reserve(newCapacity * 2); 1499 } 1500 1501 1502 @property size_t capacity() const @safe pure nothrow 1503 { 1504 // "/2" to account for the spacers 1505 return buf.capacity / 2; 1506 } 1507 1508 @property immutable(C)[] data() inout @trusted pure nothrow 1509 { 1510 return buf.data; 1511 } 1512 1513 @property size_t length() 1514 { 1515 return _length; 1516 } 1517 1518 private void putSpacer() 1519 { 1520 buf.put(_length==0? "" : " "); 1521 } 1522 1523 void put(immutable(C)[] item) 1524 { 1525 putSpacer(); 1526 buf.put(escapeShellArg(item)); 1527 _length += 2; 1528 } 1529 1530 void put(PathT!C item) 1531 { 1532 put(item.toRawString()); 1533 } 1534 1535 void put(Range)(Range items) 1536 if( 1537 isInputRange!Range && 1538 (is(ElementType!Range == string) || is(ElementType!Range == PathT!C)) 1539 ) 1540 { 1541 for(; !items.empty; items.popFront()) 1542 put(items.front); 1543 } 1544 1545 void opOpAssign(string op)(immutable(C)[] item) if(op == "~") 1546 { 1547 put(item); 1548 } 1549 1550 void opOpAssign(string op)(PathT!C item) if(op == "~") 1551 { 1552 put(item); 1553 } 1554 1555 void opOpAssign(string op, Range)(Range items) 1556 if( 1557 op == "~" && 1558 isInputRange!Range && 1559 (is(ElementType!Range == string) || is(ElementType!Range == PathT!C)) 1560 ) 1561 { 1562 put(items); 1563 } 1564 } 1565 alias Args = ArgsT!char; ///ditto 1566 1567 // The unittests in this module mainly check that all the templates compile 1568 // correctly and that the appropriate Phobos functions are correctly called. 1569 // 1570 // A completely thorough testing of the behavior of such functions is 1571 // occasionally left to Phobos itself as it is outside the scope of these tests. 1572 1573 version(unittest_scriptlike_d) 1574 unittest 1575 { 1576 import std.stdio : writeln; 1577 writeln("Running Scriptlike unittests: std.path wrappers"); 1578 1579 alias dirSep = dirSeparator; 1580 1581 foreach(C; TypeTuple!(char/+, wchar, dchar+/)) 1582 { 1583 //pragma(msg, "==="~C.stringof); 1584 1585 { 1586 auto e = Ext(".txt"); 1587 assert(e != Ext(".dat")); 1588 assert(e == Ext(".txt")); 1589 version(Windows) 1590 assert(e == Ext(".TXT")); 1591 else version(OSX) 1592 assert(e == Ext(".TXT")); 1593 else version(Posix) 1594 assert(e != Ext(".TXT")); 1595 else 1596 static assert(0, "This platform not supported."); 1597 1598 // Test the other comparison overloads 1599 assert(e != ExtT!C(".dat")); 1600 assert(e == ExtT!C(".txt")); 1601 assert(ExtT!C(".dat") != e); 1602 assert(ExtT!C(".txt") == e); 1603 assert(".dat" != e); 1604 assert(".txt" == e); 1605 1606 assert(Ext("foo")); 1607 assert(Ext("")); 1608 assert(Ext(null).str is null); 1609 assert(!Ext(null)); 1610 } 1611 1612 auto p = PathT!C(); 1613 assert(p.str == "."); 1614 assert(!p.empty); 1615 1616 assert(PathT!C("").empty); 1617 assert(Path(cast(immutable(C)[])"").empty); 1618 1619 p = Path(cast(immutable(C)[])"."); 1620 assert(!p.empty); 1621 1622 assert(Path("foo")); 1623 assert(Path("")); 1624 assert(Path(null).str is null); 1625 assert(!Path(null)); 1626 1627 version(Windows) 1628 auto testStrings = [cast(immutable(C)[])"/foo/bar", "/foo/bar/", `\foo\bar`, `\foo\bar\`]; 1629 else version(Posix) 1630 auto testStrings = [cast(immutable(C)[])"/foo/bar", "/foo/bar/"]; 1631 else 1632 static assert(0, "This platform not supported."); 1633 1634 foreach(str; testStrings) 1635 { 1636 writeln(" testing str: ", str); 1637 1638 p = PathT!C(str); 1639 assert(!p.empty); 1640 assert(p.str == dirSep~"foo"~dirSep~"bar"); 1641 1642 p = Path(str); 1643 assert(p.str == dirSep~"foo"~dirSep~"bar"); 1644 assert(p.toRawString() == p.str); 1645 assert(p.toString() == p.str.to!string()); 1646 1647 assert(p.up.toString() == dirSep~"foo"); 1648 assert(p.up.up.toString() == dirSep); 1649 1650 assert((p~"sub").toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"sub"); 1651 assert((p~"sub"~"2").toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"sub"~dirSep~"2"); 1652 assert((p~Path("sub")).toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"sub"); 1653 1654 version(Windows) 1655 assert((p~"sub dir").toString() == `"`~dirSep~"foo"~dirSep~"bar"~dirSep~"sub dir"~`"`); 1656 else version(Posix) 1657 assert((p~"sub dir").toString() == `'`~dirSep~"foo"~dirSep~"bar"~dirSep~`sub dir'`); 1658 else 1659 static assert(0, "This platform not supported."); 1660 1661 assert(("dir"~p).toString() == dirSep~"foo"~dirSep~"bar"); 1662 assert(("dir"~Path(str[1..$])).toString() == "dir"~dirSep~"foo"~dirSep~"bar"); 1663 1664 p ~= "blah"; 1665 assert(p.toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"blah"); 1666 1667 p ~= Path("more"); 1668 assert(p.toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"blah"~dirSep~"more"); 1669 1670 p ~= ".."; 1671 assert(p.toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"blah"); 1672 1673 p ~= Path(".."); 1674 assert(p.toString() == dirSep~"foo"~dirSep~"bar"); 1675 1676 p ~= "sub dir"; 1677 p ~= ".."; 1678 assert(p.toString() == dirSep~"foo"~dirSep~"bar"); 1679 1680 p ~= "filename"; 1681 assert((p~ExtT!C(".txt")).toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.txt"); 1682 assert((p~ExtT!C("txt")).toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.txt"); 1683 assert((p~ExtT!C("")).toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename"); 1684 1685 p ~= Ext(".ext"); 1686 assert(p.toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext"); 1687 assert(p.baseName().toString() == "filename.ext"); 1688 assert(p.dirName().toString() == dirSep~"foo"~dirSep~"bar"); 1689 assert(p.rootName().toString() == dirSep); 1690 assert(p.driveName().toString() == ""); 1691 assert(p.stripDrive().toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext"); 1692 version(Windows) 1693 { 1694 assert(( Path("C:"~p.toRawString()) ).toString() == "C:"~dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext"); 1695 assert(( Path("C:"~p.toRawString()) ).stripDrive().toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext"); 1696 } 1697 assert(p.extension().toString() == ".ext"); 1698 assert(p.stripExtension().toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename"); 1699 assert(p.setExtension(".txt").toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.txt"); 1700 assert(p.setExtension("txt").toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.txt"); 1701 assert(p.setExtension("").toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename"); 1702 assert(p.setExtension(ExtT!C(".txt")).toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.txt"); 1703 assert(p.setExtension(ExtT!C("txt")).toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.txt"); 1704 assert(p.setExtension(ExtT!C("")).toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename"); 1705 1706 assert(p.defaultExtension(".dat").toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext"); 1707 assert(p.stripExtension().defaultExtension(".dat").toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.dat"); 1708 1709 assert(equal(p.pathSplitter(), [dirSep, "foo", "bar", "filename.ext"])); 1710 1711 assert(p.isRooted()); 1712 version(Windows) 1713 assert(!p.isAbsolute()); 1714 else version(Posix) 1715 assert(p.isAbsolute()); 1716 else 1717 static assert(0, "This platform not supported."); 1718 1719 assert(!( Path("dir"~p.toRawString()) ).isRooted()); 1720 assert(!( Path("dir"~p.toRawString()) ).isAbsolute()); 1721 1722 version(Windows) 1723 { 1724 assert(( Path("dir"~p.toRawString()) ).absolutePath("C:/main").toString() == "C:"~dirSep~"main"~dirSep~"dir"~dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext"); 1725 assert(( Path("C:"~p.toRawString()) ).relativePath("C:/foo").toString() == "bar"~dirSep~"filename.ext"); 1726 assert(( Path("C:"~p.toRawString()) ).relativePath("C:/foo/bar").toString() == "filename.ext"); 1727 } 1728 else version(Posix) 1729 { 1730 assert(( Path("dir"~p.toRawString()) ).absolutePath("/main").toString() == dirSep~"main"~dirSep~"dir"~dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext"); 1731 assert(p.relativePath("/foo").toString() == "bar"~dirSep~"filename.ext"); 1732 assert(p.relativePath("/foo/bar").toString() == "filename.ext"); 1733 } 1734 else 1735 static assert(0, "This platform not supported."); 1736 1737 assert(p.filenameCmp(dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext") == 0); 1738 assert(p.filenameCmp(dirSep~"faa"~dirSep~"bat"~dirSep~"filename.ext") != 0); 1739 assert(p.globMatch("*foo*name.ext")); 1740 assert(!p.globMatch("*foo*Bname.ext")); 1741 1742 assert(!p.isValidFilename()); 1743 assert(p.baseName().isValidFilename()); 1744 assert(p.isValidPath()); 1745 1746 assert(p.expandTilde().toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext"); 1747 1748 assert(p != Path("/dir/subdir/filename.ext")); 1749 assert(p == Path("/foo/bar/filename.ext")); 1750 version(Windows) 1751 assert(p == Path("/FOO/BAR/FILENAME.EXT")); 1752 else version(OSX) 1753 assert(p == Path("/FOO/BAR/FILENAME.EXT")); 1754 else version(Posix) 1755 assert(p != Path("/FOO/BAR/FILENAME.EXT")); 1756 else 1757 static assert(0, "This platform not supported."); 1758 1759 // Test the other comparison overloads 1760 assert(p != PathT!C("/dir/subdir/filename.ext")); 1761 assert(p == PathT!C("/foo/bar/filename.ext")); 1762 assert(PathT!C("/dir/subdir/filename.ext") != p); 1763 assert(PathT!C("/foo/bar/filename.ext") == p); 1764 assert("/dir/subdir/filename.ext" != p); 1765 assert("/foo/bar/filename.ext" == p); 1766 } 1767 } 1768 } 1769 1770 version(unittest_scriptlike_d) 1771 unittest 1772 { 1773 // std.file.slurp seems to randomly trigger an internal std.algorithm 1774 // assert failure on DMD 2.064.2, so don't test it there. Seems 1775 // to be fixed in DMD 2.065. 1776 static import std.compiler; 1777 static if( 1778 std.compiler.vendor == std.compiler.Vendor.digitalMars || 1779 (std.compiler.version_major == 2 && std.compiler.version_minor == 64) 1780 ) 1781 enum testSlurp = false; 1782 else 1783 enum testSlurp = true; 1784 1785 import std.stdio : writeln; 1786 import core.thread; 1787 alias copy = scriptlike.path.copy; 1788 1789 writeln("Running Scriptlike unittests: std.file wrappers"); 1790 1791 immutable tempname = buildPath(tempDir(), "deleteme.script like.unit test.pid" ~ to!string(thisProcessID)); 1792 immutable tempname2 = buildPath(tempDir(), "deleteme.script like.unit test2.pid" ~ to!string(thisProcessID)); 1793 immutable tempname3 = buildPath(tempDir(), "deleteme.script like.unit test3.pid" ~ to!string(thisProcessID), "somefile"); 1794 auto tempPath = Path(tempname); 1795 auto tempPath2 = Path(tempname2); 1796 auto tempPath3 = Path(tempname3); 1797 assert(!tempname.exists()); 1798 assert(!tempname2.exists()); 1799 assert(!tempname3.dirName().exists()); 1800 assert(!tempname3.exists()); 1801 1802 { 1803 scope(exit) 1804 { 1805 if(exists(tempname)) remove(tempname); 1806 } 1807 1808 tempPath.write("stuff"); 1809 1810 tempPath.append(" more"); 1811 assert(tempPath.read(3) == "stu"); 1812 assert(tempPath.read() == "stuff more"); 1813 assert(tempPath.readText() == "stuff more"); 1814 assert(tempPath.getSize() == 10); 1815 1816 if(testSlurp) 1817 { 1818 auto parsed = tempPath.slurp!(string, string)("%s %s"); 1819 assert(equal(parsed, [tuple("stuff", "more")])); 1820 } 1821 1822 SysTime timeA, timeB, timeC; 1823 tempPath.getTimes(timeA, timeB); 1824 version(Windows) 1825 tempPath.getTimesWin(timeA, timeB, timeC); 1826 tempPath.setTimes(timeA, timeB); 1827 timeA = tempPath.timeLastModified(); 1828 timeA = tempPath.timeLastModified(timeB); 1829 1830 uint attr; 1831 attr = tempPath.getAttributes(); 1832 attr = tempPath.getLinkAttributes(); 1833 1834 assert(tempPath.exists()); 1835 assert(tempPath.isFile()); 1836 assert(tempPath.existsAsFile()); 1837 assert(!tempPath.isDir()); 1838 assert(!tempPath.existsAsDir()); 1839 assert(!tempPath.isSymlink()); 1840 assert(!tempPath.existsAsSymlink()); 1841 tempPath.remove(); 1842 assert(!tempPath.exists()); 1843 assert(!tempPath.existsAsFile()); 1844 assert(!tempPath.existsAsDir()); 1845 assert(!tempPath.existsAsSymlink()); 1846 } 1847 1848 { 1849 assert(!tempPath.exists()); 1850 assert(!tempPath2.exists()); 1851 1852 scope(exit) 1853 { 1854 if(exists(tempname)) remove(tempname); 1855 if(exists(tempname2)) remove(tempname2); 1856 } 1857 tempPath.write("ABC"); 1858 1859 assert(tempPath.existsAsFile()); 1860 assert(!tempPath2.exists()); 1861 1862 tempPath.rename(tempPath2); 1863 1864 assert(!tempPath.exists()); 1865 assert(tempPath2.existsAsFile()); 1866 1867 tempPath2.copy(tempPath); 1868 1869 assert(tempPath.existsAsFile()); 1870 assert(tempPath2.existsAsFile()); 1871 } 1872 1873 { 1874 scope(exit) 1875 { 1876 if(exists(tempname)) rmdir(tempname); 1877 if(exists(tempname3)) rmdir(tempname3); 1878 if(exists(tempname3.dirName())) rmdir(tempname3.dirName()); 1879 } 1880 1881 assert(!tempPath.exists()); 1882 assert(!tempPath3.exists()); 1883 1884 tempPath.mkdir(); 1885 assert(tempPath.exists()); 1886 assert(!tempPath.isFile()); 1887 assert(!tempPath.existsAsFile()); 1888 assert(tempPath.isDir()); 1889 assert(tempPath.existsAsDir()); 1890 assert(!tempPath.isSymlink()); 1891 assert(!tempPath.existsAsSymlink()); 1892 1893 tempPath3.mkdirRecurse(); 1894 assert(tempPath3.exists()); 1895 assert(!tempPath3.isFile()); 1896 assert(!tempPath3.existsAsFile()); 1897 assert(tempPath3.isDir()); 1898 assert(tempPath3.existsAsDir()); 1899 assert(!tempPath3.isSymlink()); 1900 assert(!tempPath3.existsAsSymlink()); 1901 1902 auto saveDirName = getcwd(); 1903 auto saveDir = Path(saveDirName); 1904 scope(exit) chdir(saveDirName); 1905 1906 tempPath.chdir(); 1907 assert(getcwd() == tempname); 1908 saveDir.chdir(); 1909 assert(getcwd() == saveDirName); 1910 1911 auto entries1 = (tempPath3~"..").dirEntries(SpanMode.shallow); 1912 assert(!entries1.empty); 1913 auto entries2 = (tempPath3~"..").dirEntries("*", SpanMode.shallow); 1914 assert(!entries2.empty); 1915 auto entries3 = (tempPath3~"..").dirEntries("TUNA TUNA THIS DOES NOT EXIST TUNA WHEE", SpanMode.shallow); 1916 assert(entries3.empty); 1917 1918 tempPath.rmdir(); 1919 assert(!tempPath.exists()); 1920 assert(!tempPath.existsAsFile()); 1921 assert(!tempPath.existsAsDir()); 1922 assert(!tempPath.existsAsSymlink()); 1923 1924 tempPath3.rmdirRecurse(); 1925 assert(!tempPath.exists()); 1926 assert(!tempPath.existsAsFile()); 1927 assert(!tempPath.existsAsDir()); 1928 assert(!tempPath.existsAsSymlink()); 1929 } 1930 1931 { 1932 version(Posix) 1933 { 1934 assert(!tempPath.exists()); 1935 assert(!tempPath2.exists()); 1936 1937 scope(exit) 1938 { 1939 if(exists(tempname2)) remove(tempname2); 1940 if(exists(tempname)) remove(tempname); 1941 } 1942 tempPath.write("DEF"); 1943 1944 tempPath.symlink(tempPath2); 1945 assert(tempPath2.exists()); 1946 assert(tempPath2.isFile()); 1947 assert(tempPath2.existsAsFile()); 1948 assert(!tempPath2.isDir()); 1949 assert(!tempPath2.existsAsDir()); 1950 assert(tempPath2.isSymlink()); 1951 assert(tempPath2.existsAsSymlink()); 1952 1953 auto linkTarget = tempPath2.readLink(); 1954 assert(linkTarget.toRawString() == tempname); 1955 } 1956 } 1957 1958 { 1959 assert(!tempPath.exists()); 1960 1961 scope(exit) 1962 { 1963 if(exists(tempname)) remove(tempname); 1964 } 1965 1966 run(`echo TestScriptStuff > `~tempPath.to!string()); 1967 assert(tempPath.exists()); 1968 assert(tempPath.isFile()); 1969 assert((cast(string)tempPath.read()).strip() == "TestScriptStuff"); 1970 tempPath.remove(); 1971 assert(!tempPath.exists()); 1972 1973 auto errlevel = tryRun(`echo TestScriptStuff > `~tempPath.to!string()); 1974 assert(tempPath.exists()); 1975 assert(tempPath.isFile()); 1976 assert((cast(string)tempPath.read()).strip() == "TestScriptStuff"); 1977 assert(errlevel == 0); 1978 tempPath.remove(); 1979 assert(!tempPath.exists()); 1980 1981 Path(getcwd()).run(`echo TestScriptStuff > `~tempPath.to!string()); 1982 Path(getcwd()).tryRun(`echo TestScriptStuff > `~tempPath.to!string()); 1983 } 1984 1985 { 1986 assert(!tempPath3.exists()); 1987 assert(!tempPath3.up.exists()); 1988 1989 scope(exit) 1990 { 1991 if(exists(tempname3)) remove(tempname3); 1992 if(exists(tempname3.dirName())) rmdir(tempname3.dirName()); 1993 } 1994 1995 tempPath3.up.mkdir(); 1996 assert(tempPath3.up.exists()); 1997 assert(tempPath3.up.isDir()); 1998 1999 tempPath3.up.run(`echo MoreTestStuff > `~tempPath3.baseName().to!string()); 2000 assert(tempPath3.exists()); 2001 assert(tempPath3.isFile()); 2002 assert((cast(string)tempPath3.read()).strip() == "MoreTestStuff"); 2003 } 2004 2005 { 2006 scope(exit) 2007 { 2008 if(exists(tempname)) rmdir(tempname); 2009 if(exists(tempname3)) rmdir(tempname3); 2010 if(exists(tempname3.dirName())) rmdir(tempname3.dirName()); 2011 } 2012 2013 assert(!tempPath.exists()); 2014 assert(!tempPath3.exists()); 2015 2016 assert(!tempPath.tryRmdir()); 2017 assert(!tempPath.tryRmdirRecurse()); 2018 assert(!tempPath.tryRemove()); 2019 assert(!tempPath.tryRename(tempPath3)); 2020 version(Posix) assert(!tempPath.trySymlink(tempPath3)); 2021 assert(!tempPath.tryCopy(tempPath3)); 2022 2023 assert(tempPath.tryMkdir()); 2024 assert(tempPath.exists()); 2025 assert(!tempPath.tryMkdir()); 2026 assert(!tempPath.tryMkdirRecurse()); 2027 2028 assert(tempPath.tryRmdir()); 2029 assert(!tempPath.exists()); 2030 2031 assert(tempPath.tryMkdirRecurse()); 2032 assert(tempPath.exists()); 2033 assert(!tempPath.tryMkdirRecurse()); 2034 } 2035 } 2036 2037 version(unittest_scriptlike_d) 2038 unittest 2039 { 2040 import std.stdio : writeln; 2041 writeln("Running Scriptlike unittests: ArgsT"); 2042 2043 Args args; 2044 args ~= Path(`some/big path/here/foobar`); 2045 args ~= "-A"; 2046 args ~= "--bcd"; 2047 args ~= "Hello World"; 2048 args ~= Path("file.ext"); 2049 2050 version(Windows) 2051 assert(args.data == `"some\big path\here\foobar" -A --bcd "Hello World" file.ext`); 2052 else version(Posix) 2053 assert(args.data == `'some/big path/here/foobar' -A --bcd 'Hello World' file.ext`); 2054 }