using System.Text; using InnovEnergy.Lib.Utils; using static System.IO.Path; namespace InnovEnergy.Lib.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(); 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}"; } #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 }