1 // The MIT License(MIT) 2 // ===================== 3 // 4 // Copyright © `2015-2017` `Lucas Meijer` 5 // 6 // Permission is hereby granted, free of charge, to any person 7 // obtaining a copy of this software and associated documentation 8 // files (the “Software”), to deal in the Software without 9 // restriction, including without limitation the rights to use, 10 // copy, modify, merge, publish, distribute, sublicense, and/or sell 11 // copies of the Software, and to permit persons to whom the 12 // Software is furnished to do so, subject to the following 13 // conditions: 14 // 15 // The above copyright notice and this permission notice shall be 16 // included in all copies or substantial portions of the Software. 17 // 18 // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 19 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 // OTHER DEALINGS IN THE SOFTWARE. 26 27 using System; 28 using System.Collections.Generic; 29 using System.IO; 30 using System.Linq; 31 using System.Text; 32 33 namespace Mono.Linker.Tests.Extensions 34 { 35 public class NPath : IEquatable<NPath>, IComparable 36 { 37 private static readonly StringComparison PathStringComparison = IsLinux() ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; 38 39 private readonly string[] _elements; 40 private readonly bool _isRelative; 41 private readonly string _driveLetter; 42 43 #region construction 44 NPath(string path)45 public NPath(string path) 46 { 47 if (path == null) 48 throw new ArgumentNullException(); 49 50 path = ParseDriveLetter(path, out _driveLetter); 51 52 if (path == "/") 53 { 54 _isRelative = false; 55 _elements = new string[] { }; 56 } 57 else 58 { 59 var split = path.Split('/', '\\'); 60 61 _isRelative = _driveLetter == null && IsRelativeFromSplitString(split); 62 63 _elements = ParseSplitStringIntoElements(split.Where(s => s.Length > 0).ToArray()); 64 } 65 } 66 NPath(string[] elements, bool isRelative, string driveLetter)67 private NPath(string[] elements, bool isRelative, string driveLetter) 68 { 69 _elements = elements; 70 _isRelative = isRelative; 71 _driveLetter = driveLetter; 72 } 73 ParseSplitStringIntoElements(IEnumerable<string> inputs)74 private string[] ParseSplitStringIntoElements(IEnumerable<string> inputs) 75 { 76 var stack = new List<string>(); 77 78 foreach (var input in inputs.Where(input => input.Length != 0)) 79 { 80 if (input == ".") 81 { 82 if ((stack.Count > 0) && (stack.Last() != ".")) 83 continue; 84 } 85 else if (input == "..") 86 { 87 if (HasNonDotDotLastElement(stack)) 88 { 89 stack.RemoveAt(stack.Count - 1); 90 continue; 91 } 92 if (!_isRelative) 93 throw new ArgumentException("You cannot create a path that tries to .. past the root"); 94 } 95 stack.Add(input); 96 } 97 return stack.ToArray(); 98 } 99 HasNonDotDotLastElement(List<string> stack)100 private static bool HasNonDotDotLastElement(List<string> stack) 101 { 102 return stack.Count > 0 && stack[stack.Count - 1] != ".."; 103 } 104 ParseDriveLetter(string path, out string driveLetter)105 private string ParseDriveLetter(string path, out string driveLetter) 106 { 107 if (path.Length >= 2 && path[1] == ':') 108 { 109 driveLetter = path[0].ToString(); 110 return path.Substring(2); 111 } 112 113 driveLetter = null; 114 return path; 115 } 116 IsRelativeFromSplitString(string[] split)117 private static bool IsRelativeFromSplitString(string[] split) 118 { 119 if (split.Length < 2) 120 return true; 121 122 return split[0].Length != 0 || !split.Any(s => s.Length > 0); 123 } 124 Combine(params string[] append)125 public NPath Combine(params string[] append) 126 { 127 return Combine(append.Select(a => new NPath(a)).ToArray()); 128 } 129 Combine(params NPath[] append)130 public NPath Combine(params NPath[] append) 131 { 132 if (!append.All(p => p.IsRelative)) 133 throw new ArgumentException("You cannot .Combine a non-relative path"); 134 135 return new NPath(ParseSplitStringIntoElements(_elements.Concat(append.SelectMany(p => p._elements))), _isRelative, _driveLetter); 136 } 137 138 public NPath Parent 139 { 140 get 141 { 142 if (_elements.Length == 0) 143 throw new InvalidOperationException("Parent is called on an empty path"); 144 145 var newElements = _elements.Take(_elements.Length - 1).ToArray(); 146 147 return new NPath(newElements, _isRelative, _driveLetter); 148 } 149 } 150 RelativeTo(NPath path)151 public NPath RelativeTo(NPath path) 152 { 153 if (!IsChildOf(path)) 154 { 155 if (!IsRelative && !path.IsRelative && _driveLetter != path._driveLetter) 156 throw new ArgumentException("Path.RelativeTo() was invoked with two paths that are on different volumes. invoked on: " + ToString() + " asked to be made relative to: " + path); 157 158 NPath commonParent = null; 159 foreach (var parent in RecursiveParents) 160 { 161 commonParent = path.RecursiveParents.FirstOrDefault(otherParent => otherParent == parent); 162 163 if (commonParent != null) 164 break; 165 } 166 167 if (commonParent == null) 168 throw new ArgumentException("Path.RelativeTo() was unable to find a common parent between " + ToString() + " and " + path); 169 170 if (IsRelative && path.IsRelative && commonParent.IsEmpty()) 171 throw new ArgumentException("Path.RelativeTo() was invoked with two relative paths that do not share a common parent. Invoked on: " + ToString() + " asked to be made relative to: " + path); 172 173 var depthDiff = path.Depth - commonParent.Depth; 174 return new NPath(Enumerable.Repeat("..", depthDiff).Concat(_elements.Skip(commonParent.Depth)).ToArray(), true, null); 175 } 176 177 return new NPath(_elements.Skip(path._elements.Length).ToArray(), true, null); 178 } 179 ChangeExtension(string extension)180 public NPath ChangeExtension(string extension) 181 { 182 ThrowIfRoot(); 183 184 var newElements = (string[])_elements.Clone(); 185 newElements[newElements.Length - 1] = Path.ChangeExtension(_elements[_elements.Length - 1], WithDot(extension)); 186 if (extension == string.Empty) 187 newElements[newElements.Length - 1] = newElements[newElements.Length - 1].TrimEnd('.'); 188 return new NPath(newElements, _isRelative, _driveLetter); 189 } 190 #endregion construction 191 192 #region inspection 193 194 public bool IsRelative 195 { 196 get { return _isRelative; } 197 } 198 199 public string FileName 200 { 201 get 202 { 203 ThrowIfRoot(); 204 205 return _elements.Last(); 206 } 207 } 208 209 public string FileNameWithoutExtension 210 { 211 get { return Path.GetFileNameWithoutExtension(FileName); } 212 } 213 214 public IEnumerable<string> Elements 215 { 216 get { return _elements; } 217 } 218 219 public int Depth 220 { 221 get { return _elements.Length; } 222 } 223 Exists(string append = R)224 public bool Exists(string append = "") 225 { 226 return Exists(new NPath(append)); 227 } 228 Exists(NPath append)229 public bool Exists(NPath append) 230 { 231 return FileExists(append) || DirectoryExists(append); 232 } 233 DirectoryExists(string append = R)234 public bool DirectoryExists(string append = "") 235 { 236 return DirectoryExists(new NPath(append)); 237 } 238 DirectoryExists(NPath append)239 public bool DirectoryExists(NPath append) 240 { 241 return Directory.Exists(Combine(append).ToString()); 242 } 243 FileExists(string append = R)244 public bool FileExists(string append = "") 245 { 246 return FileExists(new NPath(append)); 247 } 248 FileExists(NPath append)249 public bool FileExists(NPath append) 250 { 251 return File.Exists(Combine(append).ToString()); 252 } 253 254 public string ExtensionWithDot 255 { 256 get 257 { 258 if (IsRoot) 259 throw new ArgumentException("A root directory does not have an extension"); 260 261 var last = _elements.Last(); 262 var index = last.LastIndexOf("."); 263 if (index < 0) return String.Empty; 264 return last.Substring(index); 265 } 266 } 267 InQuotes()268 public string InQuotes() 269 { 270 return "\"" + ToString() + "\""; 271 } 272 InQuotes(SlashMode slashMode)273 public string InQuotes(SlashMode slashMode) 274 { 275 return "\"" + ToString(slashMode) + "\""; 276 } 277 ToString()278 public override string ToString() 279 { 280 return ToString(SlashMode.Native); 281 } 282 ToString(SlashMode slashMode)283 public string ToString(SlashMode slashMode) 284 { 285 // Check if it's linux root / 286 if (IsRoot && string.IsNullOrEmpty(_driveLetter)) 287 return Slash(slashMode).ToString(); 288 289 if (_isRelative && _elements.Length == 0) 290 return "."; 291 292 var sb = new StringBuilder(); 293 if (_driveLetter != null) 294 { 295 sb.Append(_driveLetter); 296 sb.Append(":"); 297 } 298 if (!_isRelative) 299 sb.Append(Slash(slashMode)); 300 var first = true; 301 foreach (var element in _elements) 302 { 303 if (!first) 304 sb.Append(Slash(slashMode)); 305 306 sb.Append(element); 307 first = false; 308 } 309 return sb.ToString(); 310 } 311 operator string(NPath path)312 public static implicit operator string(NPath path) 313 { 314 return path.ToString(); 315 } 316 Slash(SlashMode slashMode)317 static char Slash(SlashMode slashMode) 318 { 319 switch (slashMode) 320 { 321 case SlashMode.Backward: 322 return '\\'; 323 case SlashMode.Forward: 324 return '/'; 325 default: 326 return Path.DirectorySeparatorChar; 327 } 328 } 329 Equals(Object obj)330 public override bool Equals(Object obj) 331 { 332 if (obj == null) 333 return false; 334 335 // If parameter cannot be cast to Point return false. 336 var p = obj as NPath; 337 if ((Object)p == null) 338 return false; 339 340 return Equals(p); 341 } 342 Equals(NPath p)343 public bool Equals(NPath p) 344 { 345 if (p._isRelative != _isRelative) 346 return false; 347 348 if (!string.Equals(p._driveLetter, _driveLetter, PathStringComparison)) 349 return false; 350 351 if (p._elements.Length != _elements.Length) 352 return false; 353 354 for (var i = 0; i != _elements.Length; i++) 355 if (!string.Equals(p._elements[i], _elements[i], PathStringComparison)) 356 return false; 357 358 return true; 359 } 360 operator ==(NPath a, NPath b)361 public static bool operator ==(NPath a, NPath b) 362 { 363 // If both are null, or both are same instance, return true. 364 if (ReferenceEquals(a, b)) 365 return true; 366 367 // If one is null, but not both, return false. 368 if (((object)a == null) || ((object)b == null)) 369 return false; 370 371 // Return true if the fields match: 372 return a.Equals(b); 373 } 374 GetHashCode()375 public override int GetHashCode() 376 { 377 unchecked 378 { 379 int hash = 17; 380 // Suitable nullity checks etc, of course :) 381 hash = hash * 23 + _isRelative.GetHashCode(); 382 foreach (var element in _elements) 383 hash = hash * 23 + element.GetHashCode(); 384 if (_driveLetter != null) 385 hash = hash * 23 + _driveLetter.GetHashCode(); 386 return hash; 387 } 388 } 389 CompareTo(object obj)390 public int CompareTo(object obj) 391 { 392 if (obj == null) 393 return -1; 394 395 return this.ToString().CompareTo(((NPath)obj).ToString()); 396 } 397 operator !=(NPath a, NPath b)398 public static bool operator !=(NPath a, NPath b) 399 { 400 return !(a == b); 401 } 402 HasExtension(params string[] extensions)403 public bool HasExtension(params string[] extensions) 404 { 405 var extensionWithDotLower = ExtensionWithDot.ToLower(); 406 return extensions.Any(e => WithDot(e).ToLower() == extensionWithDotLower); 407 } 408 WithDot(string extension)409 private static string WithDot(string extension) 410 { 411 return extension.StartsWith(".") ? extension : "." + extension; 412 } 413 IsEmpty()414 private bool IsEmpty() 415 { 416 return _elements.Length == 0; 417 } 418 419 public bool IsRoot 420 { 421 get { return _elements.Length == 0 && !_isRelative; } 422 } 423 424 #endregion inspection 425 426 #region directory enumeration 427 Files(string filter, bool recurse = false)428 public IEnumerable<NPath> Files(string filter, bool recurse = false) 429 { 430 return Directory.GetFiles(ToString(), filter, recurse ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly).Select(s => new NPath(s)); 431 } 432 Files(bool recurse = false)433 public IEnumerable<NPath> Files(bool recurse = false) 434 { 435 return Files("*", recurse); 436 } 437 Contents(string filter, bool recurse = false)438 public IEnumerable<NPath> Contents(string filter, bool recurse = false) 439 { 440 return Files(filter, recurse).Concat(Directories(filter, recurse)); 441 } 442 Contents(bool recurse = false)443 public IEnumerable<NPath> Contents(bool recurse = false) 444 { 445 return Contents("*", recurse); 446 } 447 Directories(string filter, bool recurse = false)448 public IEnumerable<NPath> Directories(string filter, bool recurse = false) 449 { 450 return Directory.GetDirectories(ToString(), filter, recurse ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly).Select(s => new NPath(s)); 451 } 452 Directories(bool recurse = false)453 public IEnumerable<NPath> Directories(bool recurse = false) 454 { 455 return Directories("*", recurse); 456 } 457 458 #endregion 459 460 #region filesystem writing operations CreateFile()461 public NPath CreateFile() 462 { 463 ThrowIfRelative(); 464 ThrowIfRoot(); 465 EnsureParentDirectoryExists(); 466 File.WriteAllBytes(ToString(), new byte[0]); 467 return this; 468 } 469 CreateFile(string file)470 public NPath CreateFile(string file) 471 { 472 return CreateFile(new NPath(file)); 473 } 474 CreateFile(NPath file)475 public NPath CreateFile(NPath file) 476 { 477 if (!file.IsRelative) 478 throw new ArgumentException("You cannot call CreateFile() on an existing path with a non relative argument"); 479 return Combine(file).CreateFile(); 480 } 481 CreateDirectory()482 public NPath CreateDirectory() 483 { 484 ThrowIfRelative(); 485 486 if (IsRoot) 487 throw new NotSupportedException("CreateDirectory is not supported on a root level directory because it would be dangerous:" + ToString()); 488 489 Directory.CreateDirectory(ToString()); 490 return this; 491 } 492 CreateDirectory(string directory)493 public NPath CreateDirectory(string directory) 494 { 495 return CreateDirectory(new NPath(directory)); 496 } 497 CreateDirectory(NPath directory)498 public NPath CreateDirectory(NPath directory) 499 { 500 if (!directory.IsRelative) 501 throw new ArgumentException("Cannot call CreateDirectory with an absolute argument"); 502 503 return Combine(directory).CreateDirectory(); 504 } 505 Copy(string dest)506 public NPath Copy(string dest) 507 { 508 return Copy(new NPath(dest)); 509 } 510 Copy(string dest, Func<NPath, bool> fileFilter)511 public NPath Copy(string dest, Func<NPath, bool> fileFilter) 512 { 513 return Copy(new NPath(dest), fileFilter); 514 } 515 Copy(NPath dest)516 public NPath Copy(NPath dest) 517 { 518 return Copy(dest, p => true); 519 } 520 Copy(NPath dest, Func<NPath, bool> fileFilter)521 public NPath Copy(NPath dest, Func<NPath, bool> fileFilter) 522 { 523 ThrowIfRelative(); 524 if (dest.IsRelative) 525 dest = Parent.Combine(dest); 526 527 if (dest.DirectoryExists()) 528 return CopyWithDeterminedDestination(dest.Combine(FileName), fileFilter); 529 530 return CopyWithDeterminedDestination(dest, fileFilter); 531 } 532 MakeAbsolute()533 public NPath MakeAbsolute() 534 { 535 if (!IsRelative) 536 return this; 537 538 return NPath.CurrentDirectory.Combine(this); 539 } 540 CopyWithDeterminedDestination(NPath absoluteDestination, Func<NPath, bool> fileFilter)541 NPath CopyWithDeterminedDestination(NPath absoluteDestination, Func<NPath, bool> fileFilter) 542 { 543 if (absoluteDestination.IsRelative) 544 throw new ArgumentException("absoluteDestination must be absolute"); 545 546 if (FileExists()) 547 { 548 if (!fileFilter(absoluteDestination)) 549 return null; 550 551 absoluteDestination.EnsureParentDirectoryExists(); 552 553 File.Copy(ToString(), absoluteDestination.ToString(), true); 554 return absoluteDestination; 555 } 556 557 if (DirectoryExists()) 558 { 559 absoluteDestination.EnsureDirectoryExists(); 560 foreach (var thing in Contents()) 561 thing.CopyWithDeterminedDestination(absoluteDestination.Combine(thing.RelativeTo(this)), fileFilter); 562 return absoluteDestination; 563 } 564 565 throw new ArgumentException("Copy() called on path that doesnt exist: " + ToString()); 566 } 567 Delete(DeleteMode deleteMode = DeleteMode.Normal)568 public void Delete(DeleteMode deleteMode = DeleteMode.Normal) 569 { 570 ThrowIfRelative(); 571 572 if (IsRoot) 573 throw new NotSupportedException("Delete is not supported on a root level directory because it would be dangerous:" + ToString()); 574 575 if (FileExists()) 576 File.Delete(ToString()); 577 else if (DirectoryExists()) 578 try 579 { 580 Directory.Delete(ToString(), true); 581 } 582 catch (IOException) 583 { 584 if (deleteMode == DeleteMode.Normal) 585 throw; 586 } 587 else 588 throw new InvalidOperationException("Trying to delete a path that does not exist: " + ToString()); 589 } 590 DeleteIfExists(DeleteMode deleteMode = DeleteMode.Normal)591 public void DeleteIfExists(DeleteMode deleteMode = DeleteMode.Normal) 592 { 593 ThrowIfRelative(); 594 595 if (FileExists() || DirectoryExists()) 596 Delete(deleteMode); 597 } 598 DeleteContents()599 public NPath DeleteContents() 600 { 601 ThrowIfRelative(); 602 603 if (IsRoot) 604 throw new NotSupportedException("DeleteContents is not supported on a root level directory because it would be dangerous:" + ToString()); 605 606 if (FileExists()) 607 throw new InvalidOperationException("It is not valid to perform this operation on a file"); 608 609 if (DirectoryExists()) 610 { 611 try 612 { 613 Files().Delete(); 614 Directories().Delete(); 615 } 616 catch (IOException) 617 { 618 if (Files(true).Any()) 619 throw; 620 } 621 622 return this; 623 } 624 625 return EnsureDirectoryExists(); 626 } 627 CreateTempDirectory(string myprefix)628 public static NPath CreateTempDirectory(string myprefix) 629 { 630 var random = new Random(); 631 while (true) 632 { 633 var candidate = new NPath(Path.GetTempPath() + "/" + myprefix + "_" + random.Next()); 634 if (!candidate.Exists()) 635 return candidate.CreateDirectory(); 636 } 637 } 638 Move(string dest)639 public NPath Move(string dest) 640 { 641 return Move(new NPath(dest)); 642 } 643 Move(NPath dest)644 public NPath Move(NPath dest) 645 { 646 ThrowIfRelative(); 647 648 if (IsRoot) 649 throw new NotSupportedException("Move is not supported on a root level directory because it would be dangerous:" + ToString()); 650 651 if (dest.IsRelative) 652 return Move(Parent.Combine(dest)); 653 654 if (dest.DirectoryExists()) 655 return Move(dest.Combine(FileName)); 656 657 if (FileExists()) 658 { 659 dest.EnsureParentDirectoryExists(); 660 File.Move(ToString(), dest.ToString()); 661 return dest; 662 } 663 664 if (DirectoryExists()) 665 { 666 Directory.Move(ToString(), dest.ToString()); 667 return dest; 668 } 669 670 throw new ArgumentException("Move() called on a path that doesn't exist: " + ToString()); 671 } 672 673 #endregion 674 675 #region special paths 676 677 public static NPath CurrentDirectory 678 { 679 get 680 { 681 return new NPath(Directory.GetCurrentDirectory()); 682 } 683 } 684 685 public static NPath HomeDirectory 686 { 687 get 688 { 689 if (Path.DirectorySeparatorChar == '\\') 690 return new NPath(Environment.GetEnvironmentVariable("USERPROFILE")); 691 return new NPath(Environment.GetEnvironmentVariable("HOME")); 692 } 693 } 694 695 public static NPath SystemTemp 696 { 697 get 698 { 699 return new NPath(Path.GetTempPath()); 700 } 701 } 702 703 #endregion 704 ThrowIfRelative()705 private void ThrowIfRelative() 706 { 707 if (_isRelative) 708 throw new ArgumentException("You are attempting an operation on a Path that requires an absolute path, but the path is relative"); 709 } 710 ThrowIfRoot()711 private void ThrowIfRoot() 712 { 713 if (IsRoot) 714 throw new ArgumentException("You are attempting an operation that is not valid on a root level directory"); 715 } 716 EnsureDirectoryExists(string append = R)717 public NPath EnsureDirectoryExists(string append = "") 718 { 719 return EnsureDirectoryExists(new NPath(append)); 720 } 721 EnsureDirectoryExists(NPath append)722 public NPath EnsureDirectoryExists(NPath append) 723 { 724 var combined = Combine(append); 725 if (combined.DirectoryExists()) 726 return combined; 727 combined.EnsureParentDirectoryExists(); 728 combined.CreateDirectory(); 729 return combined; 730 } 731 EnsureParentDirectoryExists()732 public NPath EnsureParentDirectoryExists() 733 { 734 var parent = Parent; 735 parent.EnsureDirectoryExists(); 736 return parent; 737 } 738 FileMustExist()739 public NPath FileMustExist() 740 { 741 if (!FileExists()) 742 throw new FileNotFoundException("File was expected to exist : " + ToString()); 743 744 return this; 745 } 746 DirectoryMustExist()747 public NPath DirectoryMustExist() 748 { 749 if (!DirectoryExists()) 750 throw new DirectoryNotFoundException("Expected directory to exist : " + ToString()); 751 752 return this; 753 } 754 IsChildOf(string potentialBasePath)755 public bool IsChildOf(string potentialBasePath) 756 { 757 return IsChildOf(new NPath(potentialBasePath)); 758 } 759 IsChildOf(NPath potentialBasePath)760 public bool IsChildOf(NPath potentialBasePath) 761 { 762 if ((IsRelative && !potentialBasePath.IsRelative) || !IsRelative && potentialBasePath.IsRelative) 763 throw new ArgumentException("You can only call IsChildOf with two relative paths, or with two absolute paths"); 764 765 // If the other path is the root directory, then anything is a child of it as long as it's not a Windows path 766 if (potentialBasePath.IsRoot) 767 { 768 if (_driveLetter != potentialBasePath._driveLetter) 769 return false; 770 return true; 771 } 772 773 if (IsEmpty()) 774 return false; 775 776 if (Equals(potentialBasePath)) 777 return true; 778 779 return Parent.IsChildOf(potentialBasePath); 780 } 781 782 public IEnumerable<NPath> RecursiveParents 783 { 784 get 785 { 786 var candidate = this; 787 while (true) 788 { 789 if (candidate.IsEmpty()) 790 yield break; 791 792 candidate = candidate.Parent; 793 yield return candidate; 794 } 795 } 796 } 797 ParentContaining(string needle)798 public NPath ParentContaining(string needle) 799 { 800 return ParentContaining(new NPath(needle)); 801 } 802 ParentContaining(NPath needle)803 public NPath ParentContaining(NPath needle) 804 { 805 ThrowIfRelative(); 806 807 return RecursiveParents.FirstOrDefault(p => p.Exists(needle)); 808 } 809 WriteAllText(string contents)810 public NPath WriteAllText(string contents) 811 { 812 ThrowIfRelative(); 813 EnsureParentDirectoryExists(); 814 File.WriteAllText(ToString(), contents); 815 return this; 816 } 817 ReadAllText()818 public string ReadAllText() 819 { 820 ThrowIfRelative(); 821 return File.ReadAllText(ToString()); 822 } 823 WriteAllLines(string[] contents)824 public NPath WriteAllLines(string[] contents) 825 { 826 ThrowIfRelative(); 827 EnsureParentDirectoryExists(); 828 File.WriteAllLines(ToString(), contents); 829 return this; 830 } 831 ReadAllLines()832 public string[] ReadAllLines() 833 { 834 ThrowIfRelative(); 835 return File.ReadAllLines(ToString()); 836 } 837 CopyFiles(NPath destination, bool recurse, Func<NPath, bool> fileFilter = null)838 public IEnumerable<NPath> CopyFiles(NPath destination, bool recurse, Func<NPath, bool> fileFilter = null) 839 { 840 destination.EnsureDirectoryExists(); 841 return Files(recurse).Where(fileFilter ?? AlwaysTrue).Select(file => file.Copy(destination.Combine(file.RelativeTo(this)))).ToArray(); 842 } 843 MoveFiles(NPath destination, bool recurse, Func<NPath, bool> fileFilter = null)844 public IEnumerable<NPath> MoveFiles(NPath destination, bool recurse, Func<NPath, bool> fileFilter = null) 845 { 846 if (IsRoot) 847 throw new NotSupportedException("MoveFiles is not supported on this directory because it would be dangerous:" + ToString()); 848 849 destination.EnsureDirectoryExists(); 850 return Files(recurse).Where(fileFilter ?? AlwaysTrue).Select(file => file.Move(destination.Combine(file.RelativeTo(this)))).ToArray(); 851 } 852 AlwaysTrue(NPath p)853 static bool AlwaysTrue(NPath p) 854 { 855 return true; 856 } 857 IsLinux()858 private static bool IsLinux() 859 { 860 return Directory.Exists("/proc"); 861 } 862 } 863 864 public static class Extensions 865 { Copy(this IEnumerable<NPath> self, string dest)866 public static IEnumerable<NPath> Copy(this IEnumerable<NPath> self, string dest) 867 { 868 return Copy(self, new NPath(dest)); 869 } 870 Copy(this IEnumerable<NPath> self, NPath dest)871 public static IEnumerable<NPath> Copy(this IEnumerable<NPath> self, NPath dest) 872 { 873 if (dest.IsRelative) 874 throw new ArgumentException("When copying multiple files, the destination cannot be a relative path"); 875 dest.EnsureDirectoryExists(); 876 return self.Select(p => p.Copy(dest.Combine(p.FileName))).ToArray(); 877 } 878 Move(this IEnumerable<NPath> self, string dest)879 public static IEnumerable<NPath> Move(this IEnumerable<NPath> self, string dest) 880 { 881 return Move(self, new NPath(dest)); 882 } 883 Move(this IEnumerable<NPath> self, NPath dest)884 public static IEnumerable<NPath> Move(this IEnumerable<NPath> self, NPath dest) 885 { 886 if (dest.IsRelative) 887 throw new ArgumentException("When moving multiple files, the destination cannot be a relative path"); 888 dest.EnsureDirectoryExists(); 889 return self.Select(p => p.Move(dest.Combine(p.FileName))).ToArray(); 890 } 891 Delete(this IEnumerable<NPath> self)892 public static IEnumerable<NPath> Delete(this IEnumerable<NPath> self) 893 { 894 foreach (var p in self) 895 p.Delete(); 896 return self; 897 } 898 InQuotes(this IEnumerable<NPath> self, SlashMode forward = SlashMode.Native)899 public static IEnumerable<string> InQuotes(this IEnumerable<NPath> self, SlashMode forward = SlashMode.Native) 900 { 901 return self.Select(p => p.InQuotes(forward)); 902 } 903 ToNPath(this string path)904 public static NPath ToNPath(this string path) 905 { 906 return new NPath(path); 907 } 908 } 909 910 public enum SlashMode 911 { 912 Native, 913 Forward, 914 Backward 915 } 916 917 public enum DeleteMode 918 { 919 Normal, 920 Soft 921 } 922 }