wabbajack/Wabbajack.Paths/AbsolutePath.cs

223 lines
6.4 KiB
C#
Raw Permalink Normal View History

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;
2021-10-23 16:51:17 +00:00
internal readonly string[] Parts;
2021-09-27 12:42:46 +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]);
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>
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;
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
}