using System; using System.Linq; namespace Wabbajack.Paths; public struct RelativePath : IPath, IEquatable, IComparable { public readonly string[] Parts; private int _hashCode = 0; internal RelativePath(string[] parts) { Parts = parts; } public static RelativePath FromParts(string[] parts) { return new RelativePath(parts); } public static explicit operator RelativePath(string i) { var splits = i.Split(AbsolutePath.StringSplits, StringSplitOptions.RemoveEmptyEntries); if (splits.Length >= 1 && splits[0].Contains(':')) throw new PathException($"Tried to parse `{i} but `:` not valid in a path name"); return new RelativePath(splits); } public static explicit operator string(RelativePath i) { return i.ToString(); } public Extension Extension => Extension.FromPath(Parts[^1]); public RelativePath FileName => Parts.Length == 1 ? this : new RelativePath(new[] {Parts[^1]}); public RelativePath ReplaceExtension(Extension newExtension) { var paths = new string[Parts.Length]; Array.Copy(Parts, paths, paths.Length); var oldName = paths[^1]; var newName = ReplaceExtension(oldName, newExtension); paths[^1] = newName; return new RelativePath(paths); } internal static string ReplaceExtension(string oldName, Extension newExtension) { var oldExtLength = oldName.LastIndexOf(".", StringComparison.CurrentCultureIgnoreCase); if (oldExtLength < 0) oldExtLength = 0; else oldExtLength++; var newName = oldName[..^oldExtLength] + newExtension; return newName; } public RelativePath WithExtension(Extension? ext) { var parts = new string[Parts.Length]; Array.Copy(Parts, parts, Parts.Length); parts[^1] = parts[^1] + ext; return new RelativePath(parts); } public AbsolutePath RelativeTo(AbsolutePath basePath) { var newArray = new string[basePath.Parts.Length + Parts.Length]; Array.Copy(basePath.Parts, 0, newArray, 0, basePath.Parts.Length); Array.Copy(Parts, 0, newArray, basePath.Parts.Length, Parts.Length); return new AbsolutePath(newArray, basePath.PathFormat); } public readonly bool InFolder(RelativePath parent) { return ArrayExtensions.AreEqualIgnoreCase(parent.Parts, 0, Parts, 0, parent.Parts.Length); } public override string ToString() { if (Parts == null || Parts.Length == 0) return ""; return string.Join('\\', Parts); } public override int GetHashCode() { if (_hashCode != 0) return _hashCode; if (Parts == null || Parts.Length == 0) return -1; _hashCode = Parts.Aggregate(0, (current, part) => current ^ part.GetHashCode(StringComparison.CurrentCultureIgnoreCase)); return _hashCode; } public bool Equals(RelativePath other) { // ReSharper disable once ConditionIsAlwaysTrueOrFalse if (other.Parts == default) return other.Parts == Parts; if (other.Parts.Length != Parts.Length) return false; for (var idx = 0; idx < Parts.Length; idx++) if (!Parts[idx].Equals(other.Parts[idx], StringComparison.InvariantCultureIgnoreCase)) return false; return true; } public override bool Equals(object? obj) { return obj is RelativePath other && Equals(other); } public static bool operator ==(RelativePath a, RelativePath b) { return a.Equals(b); } public static bool operator !=(RelativePath a, RelativePath b) { return !a.Equals(b); } public int CompareTo(RelativePath other) { return ArrayExtensions.CompareString(Parts, other.Parts); } public int Depth => Parts.Length; public RelativePath Combine(params object[] paths) { var converted = paths.Select(p => { return p switch { string s => (RelativePath) s, RelativePath path => path, _ => throw new PathException($"Cannot cast {p} of type {p.GetType()} to Path") }; }).ToArray(); return Combine(converted); } public RelativePath Combine(params RelativePath[] paths) { var newLen = Parts.Length + paths.Sum(p => p.Parts.Length); var newParts = new string[newLen]; Array.Copy(Parts, newParts, Parts.Length); var toIdx = Parts.Length; foreach (var p in paths) { Array.Copy(p.Parts, 0, newParts, toIdx, p.Parts.Length); toIdx += p.Parts.Length; } return new RelativePath(newParts); } public RelativePath Parent { get { if (Parts.Length <= 1) throw new PathException("Can't get parent of a top level path"); var newParts = new string[Parts.Length - 1]; Array.Copy(Parts, newParts, Parts.Length - 1); return new RelativePath(newParts); } } public RelativePath WithoutExtension() { var ext = Extension; return Parts[^1][..^ext.ToString().Length].ToRelativePath(); } public RelativePath TopParent => new(Parts[..1]); public RelativePath FileNameWithoutExtension => Parts[^1][..Extension.ToString().Length].ToRelativePath(); public int Level => Parts.Length; public readonly bool EndsWith(string postfix) { return Parts[^1].EndsWith(postfix); } public bool FileNameStartsWith(string mrkinn) { return Parts[^1].StartsWith(mrkinn); } public bool StartsWith(string s) { return ToString().StartsWith(s); } public string GetPart(int i) { return Parts[i]; } }