diff options
Diffstat (limited to 'test/Mono.Linker.Tests/Extensions/NiceIO.cs')
-rw-r--r-- | test/Mono.Linker.Tests/Extensions/NiceIO.cs | 922 |
1 files changed, 922 insertions, 0 deletions
diff --git a/test/Mono.Linker.Tests/Extensions/NiceIO.cs b/test/Mono.Linker.Tests/Extensions/NiceIO.cs new file mode 100644 index 000000000..69b6f30f4 --- /dev/null +++ b/test/Mono.Linker.Tests/Extensions/NiceIO.cs @@ -0,0 +1,922 @@ +// The MIT License(MIT) +// ===================== +// +// Copyright © `2015-2017` `Lucas Meijer` +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace Mono.Linker.Tests.Extensions +{ + public class NPath : IEquatable<NPath>, IComparable + { + private static readonly StringComparison PathStringComparison = IsLinux() ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; + + private readonly string[] _elements; + private readonly bool _isRelative; + private readonly string _driveLetter; + + #region construction + + public NPath(string path) + { + if (path == null) + throw new ArgumentNullException(); + + path = ParseDriveLetter(path, out _driveLetter); + + if (path == "/") + { + _isRelative = false; + _elements = new string[] { }; + } + else + { + var split = path.Split('/', '\\'); + + _isRelative = _driveLetter == null && IsRelativeFromSplitString(split); + + _elements = ParseSplitStringIntoElements(split.Where(s => s.Length > 0).ToArray()); + } + } + + private NPath(string[] elements, bool isRelative, string driveLetter) + { + _elements = elements; + _isRelative = isRelative; + _driveLetter = driveLetter; + } + + private string[] ParseSplitStringIntoElements(IEnumerable<string> inputs) + { + var stack = new List<string>(); + + foreach (var input in inputs.Where(input => input.Length != 0)) + { + if (input == ".") + { + if ((stack.Count > 0) && (stack.Last() != ".")) + continue; + } + else if (input == "..") + { + if (HasNonDotDotLastElement(stack)) + { + stack.RemoveAt(stack.Count - 1); + continue; + } + if (!_isRelative) + throw new ArgumentException("You cannot create a path that tries to .. past the root"); + } + stack.Add(input); + } + return stack.ToArray(); + } + + private static bool HasNonDotDotLastElement(List<string> stack) + { + return stack.Count > 0 && stack[stack.Count - 1] != ".."; + } + + private string ParseDriveLetter(string path, out string driveLetter) + { + if (path.Length >= 2 && path[1] == ':') + { + driveLetter = path[0].ToString(); + return path.Substring(2); + } + + driveLetter = null; + return path; + } + + private static bool IsRelativeFromSplitString(string[] split) + { + if (split.Length < 2) + return true; + + return split[0].Length != 0 || !split.Any(s => s.Length > 0); + } + + public NPath Combine(params string[] append) + { + return Combine(append.Select(a => new NPath(a)).ToArray()); + } + + public NPath Combine(params NPath[] append) + { + if (!append.All(p => p.IsRelative)) + throw new ArgumentException("You cannot .Combine a non-relative path"); + + return new NPath(ParseSplitStringIntoElements(_elements.Concat(append.SelectMany(p => p._elements))), _isRelative, _driveLetter); + } + + public NPath Parent + { + get + { + if (_elements.Length == 0) + throw new InvalidOperationException("Parent is called on an empty path"); + + var newElements = _elements.Take(_elements.Length - 1).ToArray(); + + return new NPath(newElements, _isRelative, _driveLetter); + } + } + + public NPath RelativeTo(NPath path) + { + if (!IsChildOf(path)) + { + if (!IsRelative && !path.IsRelative && _driveLetter != path._driveLetter) + 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); + + NPath commonParent = null; + foreach (var parent in RecursiveParents) + { + commonParent = path.RecursiveParents.FirstOrDefault(otherParent => otherParent == parent); + + if (commonParent != null) + break; + } + + if (commonParent == null) + throw new ArgumentException("Path.RelativeTo() was unable to find a common parent between " + ToString() + " and " + path); + + if (IsRelative && path.IsRelative && commonParent.IsEmpty()) + 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); + + var depthDiff = path.Depth - commonParent.Depth; + return new NPath(Enumerable.Repeat("..", depthDiff).Concat(_elements.Skip(commonParent.Depth)).ToArray(), true, null); + } + + return new NPath(_elements.Skip(path._elements.Length).ToArray(), true, null); + } + + public NPath ChangeExtension(string extension) + { + ThrowIfRoot(); + + var newElements = (string[])_elements.Clone(); + newElements[newElements.Length - 1] = Path.ChangeExtension(_elements[_elements.Length - 1], WithDot(extension)); + if (extension == string.Empty) + newElements[newElements.Length - 1] = newElements[newElements.Length - 1].TrimEnd('.'); + return new NPath(newElements, _isRelative, _driveLetter); + } + #endregion construction + + #region inspection + + public bool IsRelative + { + get { return _isRelative; } + } + + public string FileName + { + get + { + ThrowIfRoot(); + + return _elements.Last(); + } + } + + public string FileNameWithoutExtension + { + get { return Path.GetFileNameWithoutExtension(FileName); } + } + + public IEnumerable<string> Elements + { + get { return _elements; } + } + + public int Depth + { + get { return _elements.Length; } + } + + public bool Exists(string append = "") + { + return Exists(new NPath(append)); + } + + public bool Exists(NPath append) + { + return FileExists(append) || DirectoryExists(append); + } + + public bool DirectoryExists(string append = "") + { + return DirectoryExists(new NPath(append)); + } + + public bool DirectoryExists(NPath append) + { + return Directory.Exists(Combine(append).ToString()); + } + + public bool FileExists(string append = "") + { + return FileExists(new NPath(append)); + } + + public bool FileExists(NPath append) + { + return File.Exists(Combine(append).ToString()); + } + + public string ExtensionWithDot + { + get + { + if (IsRoot) + throw new ArgumentException("A root directory does not have an extension"); + + var last = _elements.Last(); + var index = last.LastIndexOf("."); + if (index < 0) return String.Empty; + return last.Substring(index); + } + } + + public string InQuotes() + { + return "\"" + ToString() + "\""; + } + + public string InQuotes(SlashMode slashMode) + { + return "\"" + ToString(slashMode) + "\""; + } + + public override string ToString() + { + return ToString(SlashMode.Native); + } + + public string ToString(SlashMode slashMode) + { + // Check if it's linux root / + if (IsRoot && string.IsNullOrEmpty(_driveLetter)) + return Slash(slashMode).ToString(); + + if (_isRelative && _elements.Length == 0) + return "."; + + var sb = new StringBuilder(); + if (_driveLetter != null) + { + sb.Append(_driveLetter); + sb.Append(":"); + } + if (!_isRelative) + sb.Append(Slash(slashMode)); + var first = true; + foreach (var element in _elements) + { + if (!first) + sb.Append(Slash(slashMode)); + + sb.Append(element); + first = false; + } + return sb.ToString(); + } + + public static implicit operator string(NPath path) + { + return path.ToString(); + } + + static char Slash(SlashMode slashMode) + { + switch (slashMode) + { + case SlashMode.Backward: + return '\\'; + case SlashMode.Forward: + return '/'; + default: + return Path.DirectorySeparatorChar; + } + } + + public override bool Equals(Object obj) + { + if (obj == null) + return false; + + // If parameter cannot be cast to Point return false. + var p = obj as NPath; + if ((Object)p == null) + return false; + + return Equals(p); + } + + public bool Equals(NPath p) + { + if (p._isRelative != _isRelative) + return false; + + if (!string.Equals(p._driveLetter, _driveLetter, PathStringComparison)) + return false; + + if (p._elements.Length != _elements.Length) + return false; + + for (var i = 0; i != _elements.Length; i++) + if (!string.Equals(p._elements[i], _elements[i], PathStringComparison)) + return false; + + return true; + } + + public static bool operator ==(NPath a, NPath b) + { + // If both are null, or both are same instance, return true. + if (ReferenceEquals(a, b)) + return true; + + // If one is null, but not both, return false. + if (((object)a == null) || ((object)b == null)) + return false; + + // Return true if the fields match: + return a.Equals(b); + } + + public override int GetHashCode() + { + unchecked + { + int hash = 17; + // Suitable nullity checks etc, of course :) + hash = hash * 23 + _isRelative.GetHashCode(); + foreach (var element in _elements) + hash = hash * 23 + element.GetHashCode(); + if (_driveLetter != null) + hash = hash * 23 + _driveLetter.GetHashCode(); + return hash; + } + } + + public int CompareTo(object obj) + { + if (obj == null) + return -1; + + return this.ToString().CompareTo(((NPath)obj).ToString()); + } + + public static bool operator !=(NPath a, NPath b) + { + return !(a == b); + } + + public bool HasExtension(params string[] extensions) + { + var extensionWithDotLower = ExtensionWithDot.ToLower(); + return extensions.Any(e => WithDot(e).ToLower() == extensionWithDotLower); + } + + private static string WithDot(string extension) + { + return extension.StartsWith(".") ? extension : "." + extension; + } + + private bool IsEmpty() + { + return _elements.Length == 0; + } + + public bool IsRoot + { + get { return _elements.Length == 0 && !_isRelative; } + } + + #endregion inspection + + #region directory enumeration + + public IEnumerable<NPath> Files(string filter, bool recurse = false) + { + return Directory.GetFiles(ToString(), filter, recurse ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly).Select(s => new NPath(s)); + } + + public IEnumerable<NPath> Files(bool recurse = false) + { + return Files("*", recurse); + } + + public IEnumerable<NPath> Contents(string filter, bool recurse = false) + { + return Files(filter, recurse).Concat(Directories(filter, recurse)); + } + + public IEnumerable<NPath> Contents(bool recurse = false) + { + return Contents("*", recurse); + } + + public IEnumerable<NPath> Directories(string filter, bool recurse = false) + { + return Directory.GetDirectories(ToString(), filter, recurse ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly).Select(s => new NPath(s)); + } + + public IEnumerable<NPath> Directories(bool recurse = false) + { + return Directories("*", recurse); + } + + #endregion + + #region filesystem writing operations + public NPath CreateFile() + { + ThrowIfRelative(); + ThrowIfRoot(); + EnsureParentDirectoryExists(); + File.WriteAllBytes(ToString(), new byte[0]); + return this; + } + + public NPath CreateFile(string file) + { + return CreateFile(new NPath(file)); + } + + public NPath CreateFile(NPath file) + { + if (!file.IsRelative) + throw new ArgumentException("You cannot call CreateFile() on an existing path with a non relative argument"); + return Combine(file).CreateFile(); + } + + public NPath CreateDirectory() + { + ThrowIfRelative(); + + if (IsRoot) + throw new NotSupportedException("CreateDirectory is not supported on a root level directory because it would be dangerous:" + ToString()); + + Directory.CreateDirectory(ToString()); + return this; + } + + public NPath CreateDirectory(string directory) + { + return CreateDirectory(new NPath(directory)); + } + + public NPath CreateDirectory(NPath directory) + { + if (!directory.IsRelative) + throw new ArgumentException("Cannot call CreateDirectory with an absolute argument"); + + return Combine(directory).CreateDirectory(); + } + + public NPath Copy(string dest) + { + return Copy(new NPath(dest)); + } + + public NPath Copy(string dest, Func<NPath, bool> fileFilter) + { + return Copy(new NPath(dest), fileFilter); + } + + public NPath Copy(NPath dest) + { + return Copy(dest, p => true); + } + + public NPath Copy(NPath dest, Func<NPath, bool> fileFilter) + { + ThrowIfRelative(); + if (dest.IsRelative) + dest = Parent.Combine(dest); + + if (dest.DirectoryExists()) + return CopyWithDeterminedDestination(dest.Combine(FileName), fileFilter); + + return CopyWithDeterminedDestination(dest, fileFilter); + } + + public NPath MakeAbsolute() + { + if (!IsRelative) + return this; + + return NPath.CurrentDirectory.Combine(this); + } + + NPath CopyWithDeterminedDestination(NPath absoluteDestination, Func<NPath, bool> fileFilter) + { + if (absoluteDestination.IsRelative) + throw new ArgumentException("absoluteDestination must be absolute"); + + if (FileExists()) + { + if (!fileFilter(absoluteDestination)) + return null; + + absoluteDestination.EnsureParentDirectoryExists(); + + File.Copy(ToString(), absoluteDestination.ToString(), true); + return absoluteDestination; + } + + if (DirectoryExists()) + { + absoluteDestination.EnsureDirectoryExists(); + foreach (var thing in Contents()) + thing.CopyWithDeterminedDestination(absoluteDestination.Combine(thing.RelativeTo(this)), fileFilter); + return absoluteDestination; + } + + throw new ArgumentException("Copy() called on path that doesnt exist: " + ToString()); + } + + public void Delete(DeleteMode deleteMode = DeleteMode.Normal) + { + ThrowIfRelative(); + + if (IsRoot) + throw new NotSupportedException("Delete is not supported on a root level directory because it would be dangerous:" + ToString()); + + if (FileExists()) + File.Delete(ToString()); + else if (DirectoryExists()) + try + { + Directory.Delete(ToString(), true); + } + catch (IOException) + { + if (deleteMode == DeleteMode.Normal) + throw; + } + else + throw new InvalidOperationException("Trying to delete a path that does not exist: " + ToString()); + } + + public void DeleteIfExists(DeleteMode deleteMode = DeleteMode.Normal) + { + ThrowIfRelative(); + + if (FileExists() || DirectoryExists()) + Delete(deleteMode); + } + + public NPath DeleteContents() + { + ThrowIfRelative(); + + if (IsRoot) + throw new NotSupportedException("DeleteContents is not supported on a root level directory because it would be dangerous:" + ToString()); + + if (FileExists()) + throw new InvalidOperationException("It is not valid to perform this operation on a file"); + + if (DirectoryExists()) + { + try + { + Files().Delete(); + Directories().Delete(); + } + catch (IOException) + { + if (Files(true).Any()) + throw; + } + + return this; + } + + return EnsureDirectoryExists(); + } + + public static NPath CreateTempDirectory(string myprefix) + { + var random = new Random(); + while (true) + { + var candidate = new NPath(Path.GetTempPath() + "/" + myprefix + "_" + random.Next()); + if (!candidate.Exists()) + return candidate.CreateDirectory(); + } + } + + public NPath Move(string dest) + { + return Move(new NPath(dest)); + } + + public NPath Move(NPath dest) + { + ThrowIfRelative(); + + if (IsRoot) + throw new NotSupportedException("Move is not supported on a root level directory because it would be dangerous:" + ToString()); + + if (dest.IsRelative) + return Move(Parent.Combine(dest)); + + if (dest.DirectoryExists()) + return Move(dest.Combine(FileName)); + + if (FileExists()) + { + dest.EnsureParentDirectoryExists(); + File.Move(ToString(), dest.ToString()); + return dest; + } + + if (DirectoryExists()) + { + Directory.Move(ToString(), dest.ToString()); + return dest; + } + + throw new ArgumentException("Move() called on a path that doesn't exist: " + ToString()); + } + + #endregion + + #region special paths + + public static NPath CurrentDirectory + { + get + { + return new NPath(Directory.GetCurrentDirectory()); + } + } + + public static NPath HomeDirectory + { + get + { + if (Path.DirectorySeparatorChar == '\\') + return new NPath(Environment.GetEnvironmentVariable("USERPROFILE")); + return new NPath(Environment.GetEnvironmentVariable("HOME")); + } + } + + public static NPath SystemTemp + { + get + { + return new NPath(Path.GetTempPath()); + } + } + + #endregion + + private void ThrowIfRelative() + { + if (_isRelative) + throw new ArgumentException("You are attempting an operation on a Path that requires an absolute path, but the path is relative"); + } + + private void ThrowIfRoot() + { + if (IsRoot) + throw new ArgumentException("You are attempting an operation that is not valid on a root level directory"); + } + + public NPath EnsureDirectoryExists(string append = "") + { + return EnsureDirectoryExists(new NPath(append)); + } + + public NPath EnsureDirectoryExists(NPath append) + { + var combined = Combine(append); + if (combined.DirectoryExists()) + return combined; + combined.EnsureParentDirectoryExists(); + combined.CreateDirectory(); + return combined; + } + + public NPath EnsureParentDirectoryExists() + { + var parent = Parent; + parent.EnsureDirectoryExists(); + return parent; + } + + public NPath FileMustExist() + { + if (!FileExists()) + throw new FileNotFoundException("File was expected to exist : " + ToString()); + + return this; + } + + public NPath DirectoryMustExist() + { + if (!DirectoryExists()) + throw new DirectoryNotFoundException("Expected directory to exist : " + ToString()); + + return this; + } + + public bool IsChildOf(string potentialBasePath) + { + return IsChildOf(new NPath(potentialBasePath)); + } + + public bool IsChildOf(NPath potentialBasePath) + { + if ((IsRelative && !potentialBasePath.IsRelative) || !IsRelative && potentialBasePath.IsRelative) + throw new ArgumentException("You can only call IsChildOf with two relative paths, or with two absolute paths"); + + // If the other path is the root directory, then anything is a child of it as long as it's not a Windows path + if (potentialBasePath.IsRoot) + { + if (_driveLetter != potentialBasePath._driveLetter) + return false; + return true; + } + + if (IsEmpty()) + return false; + + if (Equals(potentialBasePath)) + return true; + + return Parent.IsChildOf(potentialBasePath); + } + + public IEnumerable<NPath> RecursiveParents + { + get + { + var candidate = this; + while (true) + { + if (candidate.IsEmpty()) + yield break; + + candidate = candidate.Parent; + yield return candidate; + } + } + } + + public NPath ParentContaining(string needle) + { + return ParentContaining(new NPath(needle)); + } + + public NPath ParentContaining(NPath needle) + { + ThrowIfRelative(); + + return RecursiveParents.FirstOrDefault(p => p.Exists(needle)); + } + + public NPath WriteAllText(string contents) + { + ThrowIfRelative(); + EnsureParentDirectoryExists(); + File.WriteAllText(ToString(), contents); + return this; + } + + public string ReadAllText() + { + ThrowIfRelative(); + return File.ReadAllText(ToString()); + } + + public NPath WriteAllLines(string[] contents) + { + ThrowIfRelative(); + EnsureParentDirectoryExists(); + File.WriteAllLines(ToString(), contents); + return this; + } + + public string[] ReadAllLines() + { + ThrowIfRelative(); + return File.ReadAllLines(ToString()); + } + + public IEnumerable<NPath> CopyFiles(NPath destination, bool recurse, Func<NPath, bool> fileFilter = null) + { + destination.EnsureDirectoryExists(); + return Files(recurse).Where(fileFilter ?? AlwaysTrue).Select(file => file.Copy(destination.Combine(file.RelativeTo(this)))).ToArray(); + } + + public IEnumerable<NPath> MoveFiles(NPath destination, bool recurse, Func<NPath, bool> fileFilter = null) + { + if (IsRoot) + throw new NotSupportedException("MoveFiles is not supported on this directory because it would be dangerous:" + ToString()); + + destination.EnsureDirectoryExists(); + return Files(recurse).Where(fileFilter ?? AlwaysTrue).Select(file => file.Move(destination.Combine(file.RelativeTo(this)))).ToArray(); + } + + static bool AlwaysTrue(NPath p) + { + return true; + } + + private static bool IsLinux() + { + return Directory.Exists("/proc"); + } + } + + public static class Extensions + { + public static IEnumerable<NPath> Copy(this IEnumerable<NPath> self, string dest) + { + return Copy(self, new NPath(dest)); + } + + public static IEnumerable<NPath> Copy(this IEnumerable<NPath> self, NPath dest) + { + if (dest.IsRelative) + throw new ArgumentException("When copying multiple files, the destination cannot be a relative path"); + dest.EnsureDirectoryExists(); + return self.Select(p => p.Copy(dest.Combine(p.FileName))).ToArray(); + } + + public static IEnumerable<NPath> Move(this IEnumerable<NPath> self, string dest) + { + return Move(self, new NPath(dest)); + } + + public static IEnumerable<NPath> Move(this IEnumerable<NPath> self, NPath dest) + { + if (dest.IsRelative) + throw new ArgumentException("When moving multiple files, the destination cannot be a relative path"); + dest.EnsureDirectoryExists(); + return self.Select(p => p.Move(dest.Combine(p.FileName))).ToArray(); + } + + public static IEnumerable<NPath> Delete(this IEnumerable<NPath> self) + { + foreach (var p in self) + p.Delete(); + return self; + } + + public static IEnumerable<string> InQuotes(this IEnumerable<NPath> self, SlashMode forward = SlashMode.Native) + { + return self.Select(p => p.InQuotes(forward)); + } + + public static NPath ToNPath(this string path) + { + return new NPath(path); + } + } + + public enum SlashMode + { + Native, + Forward, + Backward + } + + public enum DeleteMode + { + Normal, + Soft + } +}
\ No newline at end of file |