2021-09-27 12:42:46 +00:00
|
|
|
|
using System;
|
2022-09-20 23:18:32 +00:00
|
|
|
|
using System.Collections;
|
2021-09-27 12:42:46 +00:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
2022-09-20 23:18:32 +00:00
|
|
|
|
using System.Runtime.CompilerServices;
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
namespace Wabbajack.Paths;
|
|
|
|
|
|
|
|
|
|
public enum PathFormat : byte
|
2021-09-27 12:42:46 +00:00
|
|
|
|
{
|
2021-10-23 16:51:17 +00:00
|
|
|
|
Windows = 0,
|
|
|
|
|
Unix
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2022-05-28 22:53:52 +00:00
|
|
|
|
public struct AbsolutePath : IPath, IComparable<AbsolutePath>, IEquatable<AbsolutePath>
|
2021-10-23 16:51:17 +00:00
|
|
|
|
{
|
|
|
|
|
public static readonly AbsolutePath Empty = "".ToAbsolutePath();
|
|
|
|
|
public PathFormat PathFormat { get; }
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2022-05-28 22:53:52 +00:00
|
|
|
|
private int _hashCode = 0;
|
2022-09-24 21:13:29 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
internal readonly string[] Parts;
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2022-09-24 21:13:29 +00:00
|
|
|
|
public string[] PathParts => Parts == default ? Array.Empty<string>() : Parts;
|
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public Extension Extension => Extension.FromPath(Parts[^1]);
|
2022-06-24 22:16:40 +00:00
|
|
|
|
public RelativePath FileName => new(Parts[^1..]);
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
internal AbsolutePath(string[] parts, PathFormat format)
|
|
|
|
|
{
|
|
|
|
|
Parts = parts;
|
|
|
|
|
PathFormat = format;
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
internal static readonly char[] StringSplits = {'/', '\\'};
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
private static AbsolutePath Parse(string path)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(path)) return default;
|
|
|
|
|
var parts = path.Split(StringSplits, StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
|
return new AbsolutePath(parts, DetectPathType(path));
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
private static readonly HashSet<char>
|
2021-11-13 12:55:14 +00:00
|
|
|
|
DriveLetters = new("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
private static PathFormat DetectPathType(string path)
|
|
|
|
|
{
|
|
|
|
|
if (path.StartsWith("/"))
|
|
|
|
|
return PathFormat.Unix;
|
|
|
|
|
if (path.StartsWith(@"\\"))
|
|
|
|
|
return PathFormat.Windows;
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2022-08-22 16:28:11 +00:00
|
|
|
|
if (path.Length >= 2 && DriveLetters.Contains(path[0]) && path[1] == ':')
|
2021-10-23 16:51:17 +00:00
|
|
|
|
return PathFormat.Windows;
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
throw new PathException($"Invalid Path format: {path}");
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public AbsolutePath Parent
|
|
|
|
|
{
|
|
|
|
|
get
|
2021-09-27 12:42:46 +00:00
|
|
|
|
{
|
|
|
|
|
{
|
2021-10-23 16:51:17 +00:00
|
|
|
|
if (Parts.Length <= 1)
|
|
|
|
|
throw new PathException($"Path {this} does not have a parent folder");
|
|
|
|
|
var newParts = new string[Parts.Length - 1];
|
|
|
|
|
Array.Copy(Parts, newParts, newParts.Length);
|
|
|
|
|
return new AbsolutePath(newParts, PathFormat);
|
2021-09-27 12:42:46 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-10-23 16:51:17 +00:00
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2022-07-11 20:55:54 +00:00
|
|
|
|
public int Depth => Parts?.Length ?? 0;
|
2021-10-12 03:49:01 +00:00
|
|
|
|
|
2022-09-20 23:18:32 +00:00
|
|
|
|
public IEnumerable<AbsolutePath> ThisAndAllParents()
|
|
|
|
|
{
|
|
|
|
|
var p = this;
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
yield return p;
|
|
|
|
|
if (p.Depth == 1)
|
|
|
|
|
yield break;
|
|
|
|
|
p = p.Parent;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public AbsolutePath ReplaceExtension(Extension newExtension)
|
|
|
|
|
{
|
|
|
|
|
var paths = new string[Parts.Length];
|
|
|
|
|
Array.Copy(Parts, paths, paths.Length);
|
|
|
|
|
var oldName = paths[^1];
|
|
|
|
|
var newName = RelativePath.ReplaceExtension(oldName, newExtension);
|
|
|
|
|
paths[^1] = newName;
|
|
|
|
|
return new AbsolutePath(paths, PathFormat);
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public static explicit operator AbsolutePath(string input)
|
|
|
|
|
{
|
|
|
|
|
return Parse(input);
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public override string ToString()
|
|
|
|
|
{
|
|
|
|
|
if (Parts == default) return "";
|
|
|
|
|
if (PathFormat == PathFormat.Windows)
|
|
|
|
|
return string.Join('\\', Parts);
|
|
|
|
|
return '/' + string.Join('/', Parts);
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public override int GetHashCode()
|
|
|
|
|
{
|
2022-05-28 22:53:52 +00:00
|
|
|
|
if (_hashCode != 0) return _hashCode;
|
2022-08-22 15:34:19 +00:00
|
|
|
|
var result = 0;
|
|
|
|
|
foreach (var part in Parts)
|
|
|
|
|
result = result ^ part.GetHashCode(StringComparison.CurrentCultureIgnoreCase);
|
|
|
|
|
_hashCode = result;
|
2022-05-28 22:53:52 +00:00
|
|
|
|
return _hashCode;
|
2021-10-23 16:51:17 +00:00
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public override bool Equals(object? obj)
|
|
|
|
|
{
|
|
|
|
|
return obj is AbsolutePath path && Equals(path);
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public int CompareTo(AbsolutePath other)
|
|
|
|
|
{
|
|
|
|
|
return ArrayExtensions.CompareString(Parts, other.Parts);
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public bool Equals(AbsolutePath other)
|
|
|
|
|
{
|
|
|
|
|
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
|
|
|
|
|
if (other.Parts == null) return other.Parts == Parts;
|
|
|
|
|
if (Parts == null) return false;
|
|
|
|
|
if (Parts.Length != other.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;
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public RelativePath RelativeTo(AbsolutePath basePath)
|
|
|
|
|
{
|
2022-09-21 12:44:54 +00:00
|
|
|
|
if (!ArrayExtensions.AreEqualIgnoreCase(basePath.Parts, 0, Parts, 0, basePath.Parts.Length))
|
2021-10-23 16:51:17 +00:00
|
|
|
|
throw new PathException($"{basePath} is not a base path of {this}");
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
var newParts = new string[Parts.Length - basePath.Parts.Length];
|
|
|
|
|
Array.Copy(Parts, basePath.Parts.Length, newParts, 0, newParts.Length);
|
|
|
|
|
return new RelativePath(newParts);
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public bool InFolder(AbsolutePath parent)
|
|
|
|
|
{
|
2022-09-21 12:44:54 +00:00
|
|
|
|
return ArrayExtensions.AreEqualIgnoreCase(parent.Parts, 0, Parts, 0, parent.Parts.Length);
|
2021-10-23 16:51:17 +00:00
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2022-10-07 20:53:55 +00:00
|
|
|
|
public AbsolutePath Combine(params object[] paths)
|
2021-10-23 16:51:17 +00:00
|
|
|
|
{
|
|
|
|
|
var converted = paths.Select(p =>
|
2021-09-27 12:42:46 +00:00
|
|
|
|
{
|
2021-10-23 16:51:17 +00:00
|
|
|
|
return p switch
|
2021-09-27 12:42:46 +00:00
|
|
|
|
{
|
2021-10-23 16:51:17 +00:00
|
|
|
|
string s => (RelativePath) s,
|
|
|
|
|
RelativePath path => path,
|
|
|
|
|
_ => throw new PathException($"Cannot cast {p} of type {p.GetType()} to Path")
|
|
|
|
|
};
|
|
|
|
|
}).ToArray();
|
|
|
|
|
return Combine(converted);
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public AbsolutePath 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);
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
var toIdx = Parts.Length;
|
|
|
|
|
foreach (var p in paths)
|
2021-09-27 12:42:46 +00:00
|
|
|
|
{
|
2021-10-23 16:51:17 +00:00
|
|
|
|
Array.Copy(p.Parts, 0, newParts, toIdx, p.Parts.Length);
|
|
|
|
|
toIdx += p.Parts.Length;
|
2021-09-27 12:42:46 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
return new AbsolutePath(newParts, PathFormat);
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public static bool operator ==(AbsolutePath a, AbsolutePath b)
|
|
|
|
|
{
|
|
|
|
|
return a.Equals(b);
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public static bool operator !=(AbsolutePath a, AbsolutePath b)
|
|
|
|
|
{
|
|
|
|
|
return !a.Equals(b);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public AbsolutePath WithExtension(Extension? ext)
|
|
|
|
|
{
|
|
|
|
|
var parts = new string[Parts.Length];
|
|
|
|
|
Array.Copy(Parts, parts, Parts.Length);
|
|
|
|
|
parts[^1] = parts[^1] + ext;
|
|
|
|
|
return new AbsolutePath(parts, PathFormat);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public AbsolutePath AppendToName(string append)
|
|
|
|
|
{
|
|
|
|
|
return Parent.Combine((FileName.WithoutExtension() + append).ToRelativePath()
|
|
|
|
|
.WithExtension(Extension));
|
2021-09-27 12:42:46 +00:00
|
|
|
|
}
|
2022-08-22 16:28:11 +00:00
|
|
|
|
|
|
|
|
|
public static AbsolutePath ConvertNoFailure(string value)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
return (AbsolutePath) value;
|
|
|
|
|
}
|
2022-10-07 20:53:55 +00:00
|
|
|
|
catch (Exception)
|
2022-08-22 16:28:11 +00:00
|
|
|
|
{
|
|
|
|
|
return default;
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
}
|