1 /++ 2 $(H2 Scriptlike $(SCRIPTLIKE_VERSION)) 3 4 Extra Scriptlike-only functionality to complement $(MODULE_STD_FILE). 5 6 Copyright: Copyright (C) 2014-2017 Nick Sabalausky 7 License: zlib/libpng 8 Authors: Nick Sabalausky 9 +/ 10 module scriptlike.file.extras; 11 12 import std.algorithm; 13 import std.datetime; 14 import std.traits; 15 import std.typecons; 16 17 static import std.file; 18 static import std.path; 19 20 import scriptlike.core; 21 import scriptlike.path; 22 import scriptlike.file.wrappers; 23 24 /// Checks if the path exists as a directory. 25 /// 26 /// This is like $(FULL_STD_FILE isDir), but returns false instead of 27 /// throwing if the path doesn't exist. 28 bool existsAsDir(in string path) @trusted 29 { 30 yapFunc(path.escapeShellArg()); 31 return std.file.exists(path) && std.file.isDir(path); 32 } 33 ///ditto 34 bool existsAsDir(in Path path) @trusted 35 { 36 return existsAsDir(path.raw); 37 } 38 39 version(unittest_scriptlike_d) 40 unittest 41 { 42 string file, dir, notExist; 43 44 testFileOperation!("existsAsDir", "string")(() { 45 mixin(useTmpName!"file"); 46 mixin(useTmpName!"dir"); 47 mixin(useTmpName!"notExist"); 48 std.file.write(file, "abc123"); 49 std.file.mkdir(dir); 50 51 assert( !existsAsDir(file) ); 52 assert( existsAsDir(dir) ); 53 assert( !existsAsDir(notExist) ); 54 }); 55 56 testFileOperation!("existsAsDir", "Path")(() { 57 mixin(useTmpName!"file"); 58 mixin(useTmpName!"dir"); 59 mixin(useTmpName!"notExist"); 60 std.file.write(file, "abc123"); 61 std.file.mkdir(dir); 62 63 assert( !existsAsDir(Path(file)) ); 64 assert( existsAsDir(Path(dir)) ); 65 assert( !existsAsDir(Path(notExist)) ); 66 }); 67 } 68 69 /// Checks if the path exists as a file. 70 /// 71 /// This is like $(FULL_STD_FILE isFile), but returns false instead of 72 /// throwing if the path doesn't exist. 73 bool existsAsFile(in string path) @trusted 74 { 75 yapFunc(path.escapeShellArg()); 76 return std.file.exists(path) && std.file.isFile(path); 77 } 78 ///ditto 79 bool existsAsFile(in Path path) @trusted 80 { 81 return existsAsFile(path.raw); 82 } 83 84 version(unittest_scriptlike_d) 85 unittest 86 { 87 string file, dir, notExist; 88 89 testFileOperation!("existsAsFile", "string")(() { 90 mixin(useTmpName!"file"); 91 mixin(useTmpName!"dir"); 92 mixin(useTmpName!"notExist"); 93 std.file.write(file, "abc123"); 94 std.file.mkdir(dir); 95 96 assert( existsAsFile(file) ); 97 assert( !existsAsFile(dir) ); 98 assert( !existsAsFile(notExist) ); 99 }); 100 101 testFileOperation!("existsAsFile", "Path")(() { 102 mixin(useTmpName!"file"); 103 mixin(useTmpName!"dir"); 104 mixin(useTmpName!"notExist"); 105 std.file.write(file, "abc123"); 106 std.file.mkdir(dir); 107 108 assert( existsAsFile(Path(file)) ); 109 assert( !existsAsFile(Path(dir)) ); 110 assert( !existsAsFile(Path(notExist)) ); 111 }); 112 } 113 114 /// Checks if the path exists as a symlink. 115 /// 116 /// This is like $(FULL_STD_FILE isSymlink), but returns false instead of 117 /// throwing if the path doesn't exist. 118 bool existsAsSymlink()(in string path) @trusted 119 { 120 yapFunc(path.escapeShellArg()); 121 return std.file.exists(path) && std.file.isSymlink(path); 122 } 123 ///ditto 124 bool existsAsSymlink(in Path path) @trusted 125 { 126 return existsAsSymlink(path.raw); 127 } 128 129 version(unittest_scriptlike_d) 130 unittest 131 { 132 string file, dir, fileLink, dirLink, notExist; 133 134 testFileOperation!("existsAsSymlink", "string")(() { 135 mixin(useTmpName!"file"); 136 mixin(useTmpName!"dir"); 137 mixin(useTmpName!"fileLink"); 138 mixin(useTmpName!"dirLink"); 139 mixin(useTmpName!"notExist"); 140 std.file.write(file, "abc123"); 141 std.file.mkdir(dir); 142 version(Posix) 143 { 144 std.file.symlink(file, fileLink); 145 std.file.symlink(dir, dirLink); 146 } 147 148 assert( !existsAsSymlink(file) ); 149 assert( !existsAsSymlink(dir) ); 150 assert( !existsAsSymlink(notExist) ); 151 version(Posix) 152 { 153 assert( existsAsSymlink(fileLink) ); 154 assert( existsAsSymlink(dirLink) ); 155 } 156 }); 157 158 testFileOperation!("existsAsSymlink", "Path")(() { 159 mixin(useTmpName!"file"); 160 mixin(useTmpName!"dir"); 161 mixin(useTmpName!"fileLink"); 162 mixin(useTmpName!"dirLink"); 163 mixin(useTmpName!"notExist"); 164 std.file.write(file, "abc123"); 165 std.file.mkdir(dir); 166 version(Posix) 167 { 168 std.file.symlink(file, fileLink); 169 std.file.symlink(dir, dirLink); 170 } 171 172 assert( !existsAsSymlink(Path(file)) ); 173 assert( !existsAsSymlink(Path(dir)) ); 174 assert( !existsAsSymlink(Path(notExist)) ); 175 version(Posix) 176 { 177 assert( existsAsSymlink(Path(fileLink)) ); 178 assert( existsAsSymlink(Path(dirLink)) ); 179 } 180 }); 181 } 182 183 /// If 'from' exists, then rename. Otherwise, do nothing and return false. 184 /// 185 /// Supports Path and command echoing. 186 /// 187 /// Returns: Success? 188 bool tryRename(T1, T2)(T1 from, T2 to) 189 if( 190 (is(T1==string) || is(T1==Path)) && 191 (is(T2==string) || is(T2==Path)) 192 ) 193 { 194 yapFunc(from.escapeShellArg(), " -> ", to.escapeShellArg()); 195 mixin(gagEcho); 196 197 if(from.exists()) 198 { 199 rename(from, to); 200 return true; 201 } 202 203 return false; 204 } 205 206 version(unittest_scriptlike_d) 207 unittest 208 { 209 string file1, file2, notExist1, notExist2; 210 void checkPre() 211 { 212 assert(!std.file.exists(notExist1)); 213 assert(!std.file.exists(notExist2)); 214 215 assert(!std.file.exists(file2)); 216 assert(std.file.exists(file1)); 217 assert(std.file.isFile(file1)); 218 assert(cast(string) std.file.read(file1) == "abc"); 219 } 220 221 void checkPost() 222 { 223 assert(!std.file.exists(notExist1)); 224 assert(!std.file.exists(notExist2)); 225 226 assert(!std.file.exists(file1)); 227 assert(std.file.exists(file2)); 228 assert(std.file.isFile(file2)); 229 assert(cast(string) std.file.read(file2) == "abc"); 230 } 231 232 testFileOperation!("tryRename", "string,string")(() { 233 mixin(useTmpName!"file1"); 234 mixin(useTmpName!"file2"); 235 mixin(useTmpName!"notExist1"); 236 mixin(useTmpName!"notExist2"); 237 std.file.write(file1, "abc"); 238 239 checkPre(); 240 assert( tryRename(file1, file2) ); 241 assert( !tryRename(notExist1, notExist2) ); 242 mixin(checkResult); 243 }); 244 245 testFileOperation!("tryRename", "string,Path")(() { 246 mixin(useTmpName!"file1"); 247 mixin(useTmpName!"file2"); 248 mixin(useTmpName!"notExist1"); 249 mixin(useTmpName!"notExist2"); 250 std.file.write(file1, "abc"); 251 252 checkPre(); 253 assert( tryRename(file1, Path(file2)) ); 254 assert( !tryRename(notExist1, Path(notExist2)) ); 255 mixin(checkResult); 256 }); 257 258 testFileOperation!("tryRename", "Path,string")(() { 259 mixin(useTmpName!"file1"); 260 mixin(useTmpName!"file2"); 261 mixin(useTmpName!"notExist1"); 262 mixin(useTmpName!"notExist2"); 263 std.file.write(file1, "abc"); 264 265 checkPre(); 266 assert( tryRename(Path(file1), file2) ); 267 assert( !tryRename(Path(notExist1), notExist2) ); 268 mixin(checkResult); 269 }); 270 271 testFileOperation!("tryRename", "Path,Path")(() { 272 mixin(useTmpName!"file1"); 273 mixin(useTmpName!"file2"); 274 mixin(useTmpName!"notExist1"); 275 mixin(useTmpName!"notExist2"); 276 std.file.write(file1, "abc"); 277 278 checkPre(); 279 assert( tryRename(Path(file1), Path(file2)) ); 280 assert( !tryRename(Path(notExist1), Path(notExist2)) ); 281 mixin(checkResult); 282 }); 283 } 284 285 /// If 'name' exists, then remove. Otherwise, do nothing and return false. 286 /// 287 /// Supports Path, command echoing and dryrun. 288 /// 289 /// Returns: Success? 290 bool tryRemove(T)(T name) if(is(T==string) || is(T==Path)) 291 { 292 yapFunc(name.escapeShellArg()); 293 mixin(gagEcho); 294 295 if(name.exists()) 296 { 297 remove(name); 298 return true; 299 } 300 301 return false; 302 } 303 304 version(unittest_scriptlike_d) 305 unittest 306 { 307 string file, notExist; 308 void checkPre() 309 { 310 assert(std.file.exists(file)); 311 assert(std.file.isFile(file)); 312 assert(cast(string) std.file.read(file) == "abc"); 313 314 assert(!std.file.exists(notExist)); 315 } 316 317 void checkPost() 318 { 319 assert(!std.file.exists(file)); 320 assert(!std.file.exists(notExist)); 321 } 322 323 testFileOperation!("tryRemove", "string")(() { 324 mixin(useTmpName!"file"); 325 mixin(useTmpName!"notExist"); 326 std.file.write(file, "abc"); 327 328 checkPre(); 329 assert( tryRemove(file) ); 330 assert( !tryRemove(notExist) ); 331 mixin(checkResult); 332 }); 333 334 testFileOperation!("tryRemove", "Path")(() { 335 mixin(useTmpName!"file"); 336 mixin(useTmpName!"notExist"); 337 std.file.write(file, "abc"); 338 339 checkPre(); 340 assert( tryRemove(Path(file)) ); 341 assert( !tryRemove(Path(notExist)) ); 342 mixin(checkResult); 343 }); 344 } 345 346 /// If 'name' doesn't already exist, then mkdir. Otherwise, do nothing and return false. 347 /// 348 /// Supports Path and command echoing. 349 /// 350 /// Returns: Success? 351 bool tryMkdir(T)(T name) if(is(T==string) || is(T==Path)) 352 { 353 yapFunc(name.escapeShellArg()); 354 mixin(gagEcho); 355 356 if(!name.exists()) 357 { 358 mkdir(name); 359 return true; 360 } 361 362 return false; 363 } 364 365 version(unittest_scriptlike_d) 366 unittest 367 { 368 string dir, alreadyExist; 369 void checkPre() 370 { 371 assert(!std.file.exists(dir)); 372 assert(std.file.exists(alreadyExist)); 373 } 374 375 void checkPost() 376 { 377 assert(std.file.exists(dir)); 378 assert(std.file.isDir(dir)); 379 assert(std.file.exists(alreadyExist)); 380 } 381 382 testFileOperation!("tryMkdir", "string")(() { 383 mixin(useTmpName!"dir"); 384 mixin(useTmpName!"alreadyExist"); 385 std.file.mkdir(alreadyExist); 386 387 checkPre(); 388 assert( tryMkdir(dir) ); 389 assert( !tryMkdir(alreadyExist) ); 390 mixin(checkResult); 391 }); 392 393 testFileOperation!("tryMkdir", "Path")(() { 394 mixin(useTmpName!"dir"); 395 mixin(useTmpName!"alreadyExist"); 396 std.file.mkdir(alreadyExist); 397 398 checkPre(); 399 assert( tryMkdir(Path(dir)) ); 400 assert( !tryMkdir(Path(alreadyExist)) ); 401 mixin(checkResult); 402 }); 403 } 404 405 /// If 'name' doesn't already exist, then mkdirRecurse. Otherwise, do nothing and return false. 406 /// 407 /// Supports Path and command echoing. 408 /// 409 /// Returns: Success? 410 bool tryMkdirRecurse(T)(T name) if(is(T==string) || is(T==Path)) 411 { 412 yapFunc(name.escapeShellArg()); 413 mixin(gagEcho); 414 415 if(!name.exists()) 416 { 417 mkdirRecurse(name); 418 return true; 419 } 420 421 return false; 422 } 423 424 version(unittest_scriptlike_d) 425 unittest 426 { 427 string dir, alreadyExist; 428 void checkPre() 429 { 430 assert(!std.file.exists(dir)); 431 assert(std.file.exists(alreadyExist)); 432 } 433 434 void checkPost() 435 { 436 assert(std.file.exists(dir)); 437 assert(std.file.isDir(dir)); 438 assert(std.file.exists(alreadyExist)); 439 } 440 441 testFileOperation!("tryMkdirRecurse", "string")(() { 442 mixin(useTmpName!("dir", "subdir")); 443 mixin(useTmpName!"alreadyExist"); 444 std.file.mkdir(alreadyExist); 445 446 checkPre(); 447 assert( tryMkdirRecurse(dir) ); 448 assert( !tryMkdirRecurse(alreadyExist) ); 449 mixin(checkResult); 450 }); 451 452 testFileOperation!("tryMkdirRecurse", "Path")(() { 453 mixin(useTmpName!("dir", "subdir")); 454 mixin(useTmpName!"alreadyExist"); 455 std.file.mkdir(alreadyExist); 456 457 checkPre(); 458 assert( tryMkdirRecurse(Path(dir)) ); 459 assert( !tryMkdirRecurse(Path(alreadyExist)) ); 460 mixin(checkResult); 461 }); 462 } 463 464 /// If 'name' exists, then rmdir. Otherwise, do nothing and return false. 465 /// 466 /// Supports Path and command echoing. 467 /// 468 /// Returns: Success? 469 bool tryRmdir(T)(T name) if(is(T==string) || is(T==Path)) 470 { 471 yapFunc(name.escapeShellArg()); 472 mixin(gagEcho); 473 474 if(name.exists()) 475 { 476 rmdir(name); 477 return true; 478 } 479 480 return false; 481 } 482 483 version(unittest_scriptlike_d) 484 unittest 485 { 486 string dir, notExist; 487 void checkPre() 488 { 489 assert(std.file.exists(dir)); 490 assert(std.file.isDir(dir)); 491 } 492 493 void checkPost() 494 { 495 assert(!std.file.exists(dir)); 496 } 497 498 testFileOperation!("tryRmdir", "string")(() { 499 mixin(useTmpName!"dir"); 500 mixin(useTmpName!"notExist"); 501 std.file.mkdir(dir); 502 503 checkPre(); 504 assert( tryRmdir(dir) ); 505 assert( !tryRmdir(notExist) ); 506 mixin(checkResult); 507 }); 508 509 testFileOperation!("tryRmdir", "Path")(() { 510 mixin(useTmpName!"dir"); 511 mixin(useTmpName!"notExist"); 512 std.file.mkdir(dir); 513 514 checkPre(); 515 assert( tryRmdir(Path(dir)) ); 516 assert( !tryRmdir(Path(notExist)) ); 517 mixin(checkResult); 518 }); 519 } 520 521 version(docs_scriptlike_d) 522 { 523 /// Posix-only. If 'original' exists, then symlink. Otherwise, do nothing and return false. 524 /// 525 /// Supports Path and command echoing. 526 /// 527 /// Returns: Success? 528 bool trySymlink(T1, T2)(T1 original, T2 link) 529 if( 530 (is(T1==string) || is(T1==Path)) && 531 (is(T2==string) || is(T2==Path)) 532 ); 533 } 534 else version(Posix) 535 { 536 bool trySymlink(T1, T2)(T1 original, T2 link) 537 if( 538 (is(T1==string) || is(T1==Path)) && 539 (is(T2==string) || is(T2==Path)) 540 ) 541 { 542 yapFunc("[original] ", original.escapeShellArg(), " : [symlink] ", link.escapeShellArg()); 543 mixin(gagEcho); 544 545 if(original.exists()) 546 { 547 symlink(original, link); 548 return true; 549 } 550 551 return false; 552 } 553 554 version(unittest_scriptlike_d) 555 unittest 556 { 557 string file, link, notExistFile, notExistLink; 558 void checkPre() 559 { 560 assert(std.file.exists(file)); 561 assert(std.file.isFile(file)); 562 assert(cast(string) std.file.read(file) == "abc123"); 563 564 assert(!std.file.exists(link)); 565 566 assert(!std.file.exists(notExistFile)); 567 assert(!std.file.exists(notExistLink)); 568 } 569 570 void checkPost() 571 { 572 assert(std.file.exists(file)); 573 assert(std.file.isFile(file)); 574 assert(cast(string) std.file.read(file) == "abc123"); 575 576 assert(std.file.exists(link)); 577 assert(std.file.isSymlink(link)); 578 assert(std.file.readLink(link) == file); 579 assert(cast(string) std.file.read(link) == "abc123"); 580 581 assert(!std.file.exists(notExistFile)); 582 assert(!std.file.exists(notExistLink)); 583 } 584 585 testFileOperation!("trySymlink", "string,string")(() { 586 mixin(useTmpName!"file"); 587 mixin(useTmpName!"link"); 588 mixin(useTmpName!"notExistFile"); 589 mixin(useTmpName!"notExistLink"); 590 std.file.write(file, "abc123"); 591 592 checkPre(); 593 assert( trySymlink(file, link) ); 594 assert( !trySymlink(notExistFile, notExistLink) ); 595 mixin(checkResult); 596 }); 597 598 testFileOperation!("trySymlink", "string,Path")(() { 599 mixin(useTmpName!"file"); 600 mixin(useTmpName!"link"); 601 mixin(useTmpName!"notExistFile"); 602 mixin(useTmpName!"notExistLink"); 603 std.file.write(file, "abc123"); 604 605 checkPre(); 606 assert( trySymlink(file, Path(link)) ); 607 assert( !trySymlink(notExistFile, Path(notExistLink)) ); 608 mixin(checkResult); 609 }); 610 611 testFileOperation!("trySymlink", "Path,string")(() { 612 mixin(useTmpName!"file"); 613 mixin(useTmpName!"link"); 614 mixin(useTmpName!"notExistFile"); 615 mixin(useTmpName!"notExistLink"); 616 std.file.write(file, "abc123"); 617 618 checkPre(); 619 assert( trySymlink(Path(file), link) ); 620 assert( !trySymlink(Path(notExistFile), notExistLink) ); 621 mixin(checkResult); 622 }); 623 624 testFileOperation!("trySymlink", "Path,Path")(() { 625 mixin(useTmpName!"file"); 626 mixin(useTmpName!"link"); 627 mixin(useTmpName!"notExistFile"); 628 mixin(useTmpName!"notExistLink"); 629 std.file.write(file, "abc123"); 630 631 checkPre(); 632 assert( trySymlink(Path(file), Path(link)) ); 633 assert( !trySymlink(Path(notExistFile), Path(notExistLink)) ); 634 mixin(checkResult); 635 }); 636 } 637 } 638 639 /// If 'from' exists, then copy. Otherwise, do nothing and return false. 640 /// 641 /// Supports Path and command echoing. 642 /// 643 /// Returns: Success? 644 bool tryCopy(T1, T2)(T1 from, T2 to) 645 if( 646 (is(T1==string) || is(T1==Path)) && 647 (is(T2==string) || is(T2==Path)) 648 ) 649 { 650 yapFunc(from.escapeShellArg(), " -> ", to.escapeShellArg()); 651 mixin(gagEcho); 652 653 if(from.exists()) 654 { 655 copy(from, to); 656 return true; 657 } 658 659 return false; 660 } 661 662 version(unittest_scriptlike_d) 663 unittest 664 { 665 string file1, file2, notExist1, notExist2; 666 void checkPre() 667 { 668 assert(!std.file.exists(notExist1)); 669 assert(!std.file.exists(notExist2)); 670 671 assert(std.file.exists(file1)); 672 assert(std.file.isFile(file1)); 673 assert(cast(string) std.file.read(file1) == "abc"); 674 675 assert(!std.file.exists(file2)); 676 } 677 678 void checkPost() 679 { 680 assert(!std.file.exists(notExist1)); 681 assert(!std.file.exists(notExist2)); 682 683 assert(std.file.exists(file1)); 684 assert(std.file.isFile(file1)); 685 assert(cast(string) std.file.read(file1) == "abc"); 686 687 assert(std.file.exists(file2)); 688 assert(std.file.isFile(file2)); 689 assert(cast(string) std.file.read(file2) == "abc"); 690 } 691 692 testFileOperation!("tryCopy", "string,string")(() { 693 mixin(useTmpName!"file1"); 694 mixin(useTmpName!"file2"); 695 mixin(useTmpName!"notExist1"); 696 mixin(useTmpName!"notExist2"); 697 std.file.write(file1, "abc"); 698 699 checkPre(); 700 assert( tryCopy(file1, file2) ); 701 assert( !tryCopy(notExist1, notExist2) ); 702 mixin(checkResult); 703 }); 704 705 testFileOperation!("tryCopy", "string,Path")(() { 706 mixin(useTmpName!"file1"); 707 mixin(useTmpName!"file2"); 708 mixin(useTmpName!"notExist1"); 709 mixin(useTmpName!"notExist2"); 710 std.file.write(file1, "abc"); 711 712 checkPre(); 713 assert( tryCopy(file1, Path(file2)) ); 714 assert( !tryCopy(notExist1, Path(notExist2)) ); 715 mixin(checkResult); 716 }); 717 718 testFileOperation!("tryCopy", "Path,string")(() { 719 mixin(useTmpName!"file1"); 720 mixin(useTmpName!"file2"); 721 mixin(useTmpName!"notExist1"); 722 mixin(useTmpName!"notExist2"); 723 std.file.write(file1, "abc"); 724 725 checkPre(); 726 assert( tryCopy(Path(file1), file2) ); 727 assert( !tryCopy(Path(notExist1), notExist2) ); 728 mixin(checkResult); 729 }); 730 731 testFileOperation!("tryCopy", "Path,Path")(() { 732 mixin(useTmpName!"file1"); 733 mixin(useTmpName!"file2"); 734 mixin(useTmpName!"notExist1"); 735 mixin(useTmpName!"notExist2"); 736 std.file.write(file1, "abc"); 737 738 checkPre(); 739 assert( tryCopy(Path(file1), Path(file2)) ); 740 assert( !tryCopy(Path(notExist1), Path(notExist2)) ); 741 mixin(checkResult); 742 }); 743 } 744 745 /// If 'name' exists, then rmdirRecurse. Otherwise, do nothing and return false. 746 /// 747 /// Supports Path and command echoing. 748 /// 749 /// Returns: Success? 750 bool tryRmdirRecurse(T)(T name) if(is(T==string) || is(T==Path)) 751 { 752 yapFunc(name.escapeShellArg()); 753 mixin(gagEcho); 754 755 if(name.exists()) 756 { 757 rmdirRecurse(name); 758 return true; 759 } 760 761 return false; 762 } 763 764 version(unittest_scriptlike_d) 765 unittest 766 { 767 string dir, notExist; 768 void checkPre() 769 { 770 assert(std.file.exists(dir)); 771 assert(std.file.isDir(dir)); 772 773 assert(!std.file.exists( notExist )); 774 } 775 776 void checkPost() 777 { 778 assert(!std.file.exists( std.path.dirName(dir) )); 779 780 assert(!std.file.exists( notExist )); 781 } 782 783 testFileOperation!("tryRmdirRecurse", "string")(() { 784 mixin(useTmpName!("dir", "subdir")); 785 mixin(useTmpName!"notExist"); 786 std.file.mkdirRecurse(dir); 787 788 checkPre(); 789 assert(tryRmdirRecurse( std.path.dirName(dir) )); 790 assert(!tryRmdirRecurse( notExist )); 791 mixin(checkResult); 792 }); 793 794 testFileOperation!("tryRmdirRecurse", "Path")(() { 795 mixin(useTmpName!("dir", "subdir")); 796 mixin(useTmpName!"notExist"); 797 std.file.mkdirRecurse(dir); 798 799 checkPre(); 800 assert(tryRmdirRecurse(Path( std.path.dirName(dir) ))); 801 assert(!tryRmdirRecurse(Path( notExist ) )); 802 mixin(checkResult); 803 }); 804 } 805 806 /// Delete `name` regardless of whether it's a file or directory. 807 /// If it's a directory, it's deleted recursively, via 808 /// $(API_FILE_WRAP rmdirRecurse). Throws if the file/directory doesn't exist. 809 /// 810 /// If you just want to make sure a file/dir is gone, and don't care whether 811 /// it already exists or not, consider using `tryRemovePath` instead. 812 /// 813 /// Supports Path and command echoing. 814 void removePath(T)(T name) if(is(T==string) || is(T==Path)) 815 { 816 yapFunc(name.escapeShellArg()); 817 818 if(name.exists() && name.isDir()) 819 rmdirRecurse(name); 820 else 821 remove(name); 822 } 823 824 version(unittest_scriptlike_d) 825 unittest 826 { 827 import std.exception; 828 string file, dir, notExist; 829 830 void checkPre() 831 { 832 assert(std.file.exists(file)); 833 assert(std.file.isFile(file)); 834 835 assert(std.file.exists(dir)); 836 assert(std.file.isDir(dir)); 837 838 assert(!std.file.exists( notExist )); 839 } 840 841 void checkPost() 842 { 843 assert(!std.file.exists( file )); 844 assert(!std.file.exists( std.path.dirName(dir) )); 845 assert(!std.file.exists( notExist )); 846 } 847 848 testFileOperation!("removePath", "string")(() { 849 mixin(useTmpName!"file"); 850 mixin(useTmpName!("dir", "subdir")); 851 mixin(useTmpName!"notExist"); 852 std.file.write(file, "abc"); 853 std.file.mkdirRecurse(dir); 854 855 checkPre(); 856 removePath( file ); 857 removePath( std.path.dirName(dir) ); 858 if(scriptlikeDryRun) 859 removePath( notExist ); 860 else 861 assertThrown(removePath( notExist )); 862 mixin(checkResult); 863 }); 864 865 testFileOperation!("removePath", "Path")(() { 866 mixin(useTmpName!"file"); 867 mixin(useTmpName!("dir", "subdir")); 868 mixin(useTmpName!"notExist"); 869 std.file.write(file, "abc"); 870 std.file.mkdirRecurse(dir); 871 872 checkPre(); 873 removePath(Path( file )); 874 removePath(Path( std.path.dirName(dir) )); 875 if(scriptlikeDryRun) 876 removePath(Path( notExist )); 877 else 878 assertThrown(removePath(Path( notExist ) )); 879 mixin(checkResult); 880 }); 881 } 882 883 /// If `name` exists, then delete it regardless of whether it's a file or 884 /// directory. If it doesn't already exist, do nothing and return false. 885 /// 886 /// If you want an exception to be thrown if `name` doesn't already exist, 887 /// use `removePath` instead. 888 /// 889 /// Supports Path and command echoing. 890 /// 891 /// Returns: Success? 892 bool tryRemovePath(T)(T name) if(is(T==string) || is(T==Path)) 893 { 894 yapFunc(name.escapeShellArg()); 895 mixin(gagEcho); 896 897 if(name.exists()) 898 { 899 removePath(name); 900 return true; 901 } 902 903 return false; 904 } 905 906 version(unittest_scriptlike_d) 907 unittest 908 { 909 string file, dir, notExist; 910 911 void checkPre() 912 { 913 assert(std.file.exists(file)); 914 assert(std.file.isFile(file)); 915 916 assert(std.file.exists(dir)); 917 assert(std.file.isDir(dir)); 918 919 assert(!std.file.exists( notExist )); 920 } 921 922 void checkPost() 923 { 924 assert(!std.file.exists( file )); 925 assert(!std.file.exists( std.path.dirName(dir) )); 926 assert(!std.file.exists( notExist )); 927 } 928 929 testFileOperation!("tryRemovePath", "string")(() { 930 mixin(useTmpName!"file"); 931 mixin(useTmpName!("dir", "subdir")); 932 mixin(useTmpName!"notExist"); 933 std.file.write(file, "abc"); 934 std.file.mkdirRecurse(dir); 935 936 checkPre(); 937 assert(tryRemovePath( file )); 938 assert(tryRemovePath( std.path.dirName(dir) )); 939 assert(!tryRemovePath( notExist )); 940 mixin(checkResult); 941 }); 942 943 testFileOperation!("tryRemovePath", "Path")(() { 944 mixin(useTmpName!"file"); 945 mixin(useTmpName!("dir", "subdir")); 946 mixin(useTmpName!"notExist"); 947 std.file.write(file, "abc"); 948 std.file.mkdirRecurse(dir); 949 950 checkPre(); 951 assert(tryRemovePath(Path( file ))); 952 assert(tryRemovePath(Path( std.path.dirName(dir) ))); 953 assert(!tryRemovePath(Path( notExist ) )); 954 mixin(checkResult); 955 }); 956 } 957 958 version(docs_scriptlike_d) 959 { 960 /// Posix-only. Check the user (ie "owner") executable bit of a file. File must exist. 961 bool isUserExec(Path path); 962 ///ditto 963 bool isUserExec(string path); 964 965 /// Posix-only. Check the group executable bit of a file. File must exist. 966 bool isGroupExec(Path path); 967 ///ditto 968 bool isGroupExec(string path); 969 970 /// Posix-only. Check the world (ie "other") executable bit of a file. File must exist. 971 bool isWorldExec(Path path); 972 ///ditto 973 bool isWorldExec(string path); 974 } 975 else version(Posix) 976 { 977 bool isUserExec(Path path) 978 { 979 return isUserExec(path.raw); 980 } 981 982 bool isUserExec(string path) 983 { 984 import core.sys.posix.sys.stat; 985 return !!(getAttributes(path) & S_IXUSR); 986 } 987 988 bool isGroupExec(Path path) 989 { 990 return isGroupExec(path.raw); 991 } 992 993 bool isGroupExec(string path) 994 { 995 import core.sys.posix.sys.stat; 996 return !!(getAttributes(path) & S_IXGRP); 997 } 998 999 bool isWorldExec(Path path) 1000 { 1001 return isUserExec(path.raw); 1002 } 1003 1004 bool isWorldExec(string path) 1005 { 1006 import core.sys.posix.sys.stat; 1007 return !!(getAttributes(path) & S_IXOTH); 1008 } 1009 } 1010 1011 version(Posix) 1012 version(unittest_scriptlike_d) 1013 unittest 1014 { 1015 import std.stdio; 1016 writeln("Running Scriptlike unittests: isUserExec / isGroupExec / isWorldExec"); stdout.flush(); 1017 1018 mixin(useSandbox); 1019 1020 import scriptlike.process : run; 1021 1022 writeFile("noX.txt", "Hi"); 1023 writeFile("userX.txt", "Hi"); 1024 writeFile("groupX.txt", "Hi"); 1025 writeFile("otherX.txt", "Hi"); 1026 writeFile("allX.txt", "Hi"); 1027 run("chmod -x noX.txt"); 1028 run("chmod u+x,go-x userX.txt"); 1029 run("chmod g+x,uo-x groupX.txt"); 1030 run("chmod o+x,ug-x otherX.txt"); 1031 run("chmod +x allX.txt"); 1032 1033 assert(!isUserExec("noX.txt")); 1034 assert(isUserExec("userX.txt")); 1035 assert(!isUserExec("groupX.txt")); 1036 assert(!isUserExec("otherX.txt")); 1037 assert(isUserExec("allX.txt")); 1038 1039 assert(!isGroupExec("noX.txt")); 1040 assert(!isGroupExec("userX.txt")); 1041 assert(isGroupExec("groupX.txt")); 1042 assert(!isGroupExec("otherX.txt")); 1043 assert(isGroupExec("allX.txt")); 1044 1045 assert(!isWorldExec("noX.txt")); 1046 assert(!isWorldExec("userX.txt")); 1047 assert(!isWorldExec("groupX.txt")); 1048 assert(isWorldExec("otherX.txt")); 1049 assert(isWorldExec("allX.txt")); 1050 }