229 lines
6.1 KiB
C#
229 lines
6.1 KiB
C#
|
using System.Text;
|
||
|
using InnovEnergy.SysTools.Remote;
|
||
|
using InnovEnergy.SysTools.Utils;
|
||
|
using static System.IO.Path;
|
||
|
|
||
|
namespace InnovEnergy.SysTools;
|
||
|
|
||
|
public readonly struct SysPath
|
||
|
{
|
||
|
private readonly String _Path;
|
||
|
|
||
|
public static SysPath Root { get; } = "/";
|
||
|
|
||
|
public Boolean IsRelative => _Path.ElementAtOrDefault(0) == '.';
|
||
|
public Boolean IsAbsolute => _Path.ElementAtOrDefault(0) == '/';
|
||
|
public Boolean IsEnvRef => !_Path.Contains('/');
|
||
|
public Boolean IsRoot => _Path == "/";
|
||
|
|
||
|
public SysPath Parent => this / "..";
|
||
|
public String Head => _Path.AfterLast(DirectorySeparatorChar);
|
||
|
public String Tail => _Path.UntilLast(DirectorySeparatorChar);
|
||
|
public String Extension => _Path.AfterLast('.');
|
||
|
|
||
|
public SysPath(String path)
|
||
|
{
|
||
|
if (path.IsNullOrWhiteSpace())
|
||
|
throw new ArgumentException(nameof(path));
|
||
|
|
||
|
path = path
|
||
|
.Replace(@"\", "/")
|
||
|
.Trim()
|
||
|
.TrimEnd('/');
|
||
|
|
||
|
if (path.Length == 0) _Path = "/"; // root
|
||
|
else if (!path.Contains('/')) _Path = path; // env
|
||
|
else
|
||
|
{
|
||
|
_Path = Normalize(path);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
public static SysPath FromString(String strPath) => new SysPath(strPath);
|
||
|
|
||
|
public static SysPath FromUri(String uriPath)
|
||
|
{
|
||
|
var uri = new Uri(uriPath);
|
||
|
return new SysPath(uri.AbsolutePath);
|
||
|
}
|
||
|
|
||
|
public SysPath ToRelative()
|
||
|
{
|
||
|
if (IsRelative)
|
||
|
return this;
|
||
|
|
||
|
if (IsAbsolute)
|
||
|
return $".{this}";
|
||
|
|
||
|
return $"./{this}";
|
||
|
}
|
||
|
|
||
|
public SysPath ToAbsolute()
|
||
|
{
|
||
|
if (IsAbsolute)
|
||
|
return this;
|
||
|
|
||
|
if (IsRelative)
|
||
|
return $"/{_Path.AfterFirst('/')}";
|
||
|
|
||
|
return $"/{this}";
|
||
|
}
|
||
|
|
||
|
public SysPath ToEnvRef()
|
||
|
{
|
||
|
if (IsEnvRef)
|
||
|
return this;
|
||
|
|
||
|
return $"/{_Path.AfterFirst('/')}";
|
||
|
}
|
||
|
|
||
|
|
||
|
private static String Normalize(String path)
|
||
|
{
|
||
|
if (path == "/")
|
||
|
return path;
|
||
|
|
||
|
var components = path.Split('/');
|
||
|
|
||
|
if (components.Length == 1)
|
||
|
return path; // envRef
|
||
|
|
||
|
var stack = new Stack<String>();
|
||
|
|
||
|
foreach (var comp in components)
|
||
|
{
|
||
|
if (comp == ".")
|
||
|
{
|
||
|
if (stack.Count == 0)
|
||
|
stack.Push(comp);
|
||
|
|
||
|
// otherwise just ignore it
|
||
|
}
|
||
|
else if (comp == "..")
|
||
|
{
|
||
|
if (stack.Count == 0 || stack.Peek() == "..")
|
||
|
stack.Push(comp);
|
||
|
else
|
||
|
{
|
||
|
if (stack.Count == 1) // one parent
|
||
|
{
|
||
|
if (stack.Peek() == ".")
|
||
|
{
|
||
|
stack.Pop();
|
||
|
stack.Push("..");
|
||
|
}
|
||
|
else if (stack.Peek() == "")
|
||
|
throw new Exception("Malformed path. Cannot ascend above root.");
|
||
|
else
|
||
|
stack.Pop();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
stack.Pop();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
stack.Push(comp);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var sb = new StringBuilder();
|
||
|
|
||
|
if (stack.Count == 0)
|
||
|
return ".";
|
||
|
|
||
|
foreach (var comp in stack.Reverse())
|
||
|
{
|
||
|
sb.Append(comp);
|
||
|
sb.Append("/");
|
||
|
}
|
||
|
|
||
|
if (sb.Length > 1)
|
||
|
sb.Remove(sb.Length - 1, 1);
|
||
|
|
||
|
return sb.ToString();
|
||
|
}
|
||
|
|
||
|
|
||
|
public SysPath RelativeTo(SysPath referencePath)
|
||
|
{
|
||
|
// TODO
|
||
|
|
||
|
if (IsRelative || referencePath.IsRelative )
|
||
|
throw new InvalidOperationException();
|
||
|
|
||
|
var refPath = referencePath._Path.Split();
|
||
|
var path = _Path.Split();
|
||
|
|
||
|
var prefixSize = Enumerable
|
||
|
.Zip(refPath, path, (l, r) => l == r)
|
||
|
.TakeWhile(e => e)
|
||
|
.Count();
|
||
|
|
||
|
var nUps = refPath.Length - prefixSize;
|
||
|
|
||
|
var dirs = Enumerable
|
||
|
.Repeat("..", nUps)
|
||
|
.Concat(path.Skip(prefixSize));
|
||
|
|
||
|
var relative = String.Join(DirectorySeparatorChar.ToString(), dirs);
|
||
|
|
||
|
return relative.IsNullOrEmpty() ? "." : relative;
|
||
|
}
|
||
|
|
||
|
public Boolean IsSubPathOf(SysPath referencePath)
|
||
|
{
|
||
|
// TODO: verify
|
||
|
|
||
|
if (this == referencePath)
|
||
|
return true;
|
||
|
|
||
|
if (!_Path.StartsWith(referencePath))
|
||
|
return false;
|
||
|
|
||
|
return _Path.Length > referencePath._Path.Length &&
|
||
|
_Path[referencePath._Path.Length] == DirectorySeparatorChar;
|
||
|
}
|
||
|
|
||
|
public SysPath Append(SysPath right)
|
||
|
{
|
||
|
if (right.IsAbsolute)
|
||
|
throw new ArgumentException("right path cannot be absolute", nameof(right));
|
||
|
|
||
|
return $"{_Path}/{right}";
|
||
|
}
|
||
|
|
||
|
public RemotePath At(SshHost host) => new RemotePath(host, this);
|
||
|
|
||
|
#region overrides
|
||
|
|
||
|
public override Boolean Equals(Object obj) => _Path.Equals(obj?.ToString());
|
||
|
public override Int32 GetHashCode() => _Path.GetHashCode();
|
||
|
public override String ToString() => _Path;
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region operators
|
||
|
|
||
|
public static implicit operator String(SysPath sysPath) => sysPath._Path;
|
||
|
public static implicit operator SysPath(String path) => new SysPath(path);
|
||
|
|
||
|
public static SysPath operator /(SysPath left, SysPath right) => left.Append(right);
|
||
|
public static SysPath operator /(String left, SysPath right) => FromString(left).Append(right);
|
||
|
public static SysPath operator /(SysPath left, String right) => left.Append(right);
|
||
|
|
||
|
public static Boolean operator ==(SysPath left, SysPath right) => left._Path == right._Path;
|
||
|
public static Boolean operator !=(SysPath left, SysPath right) => left._Path != right._Path;
|
||
|
|
||
|
public static Boolean operator ==(SysPath left, String right) => left._Path == right;
|
||
|
public static Boolean operator !=(SysPath left, String right) => left._Path != right;
|
||
|
|
||
|
public static Boolean operator ==(String left, SysPath right) => left == right._Path;
|
||
|
public static Boolean operator !=(String left, SysPath right) => left != right._Path;
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
}
|