/// <exception cref="ArgumentException">running on Windows host, and unmanaged call failed</exception> /// <exception cref="FileNotFoundException">running on Windows host, and either path is not a regular file or directory</exception> /// <remarks>Algorithm for Windows taken from https://stackoverflow.com/a/485516/7467292</remarks> public static string GetRelativePath(string fromPath, string toPath) { if (OSTailoredCode.IsUnixHost) { #if true return(PathManager.IsSubfolder(toPath, fromPath) ? "./" + OSTailoredCode.SimpleSubshell("realpath", $"--relative-to=\"{toPath}\" \"{fromPath}\"", $"invalid path {fromPath} or missing realpath binary") : fromPath); #else // written for Unix port but may be useful for .NET Core // algorithm taken from https://stackoverflow.com/a/340454/7467292 var dirSepChar = Path.DirectorySeparatorChar; var fromUri = new Uri(fromPath.EndsWith(dirSepChar.ToString()) ? fromPath : fromPath + dirSepChar); var toUri = new Uri(toPath.EndsWith(dirSepChar.ToString()) ? toPath : toPath + dirSepChar); if (fromUri.Scheme != toUri.Scheme) { return(toPath); } var relativePath = Uri.UnescapeDataString(fromUri.MakeRelativeUri(toUri).ToString()); return((toUri.Scheme.Equals(Uri.UriSchemeFile, StringComparison.OrdinalIgnoreCase) ? relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar) : relativePath ).TrimEnd(dirSepChar)); #endif }
/// <summary> /// Takes an absolute path and attempts to convert it to a relative, based on the system, /// or global base if no system is supplied, if it is not a subfolder of the base, it will return the path unaltered /// </summary> public static string TryMakeRelative(string absolutePath, string system = null) { var parentPath = string.IsNullOrWhiteSpace(system) ? GetGlobalBasePathAbsolute() : MakeAbsolutePath(GetPlatformBase(system), system); #if true if (!IsSubfolder(parentPath, absolutePath)) { return(absolutePath); } return(OSTailoredCode.CurrentOS == OSTailoredCode.DistinctOS.Windows ? absolutePath.Replace(parentPath, ".") : "./" + OSTailoredCode.SimpleSubshell("realpath", $"--relative-to=\"{parentPath}\" \"{absolutePath}\"", $"invalid path {absolutePath} or missing realpath binary")); #else // written for Unix port but may be useful for .NET Core if (!IsSubfolder(parentPath, absolutePath)) { return(OSTailoredCode.CurrentOS == OSTailoredCode.DistinctOS.Windows || parentPath.TrimEnd('.') != $"{absolutePath}/" ? absolutePath : "."); } return(OSTailoredCode.CurrentOS == OSTailoredCode.DistinctOS.Windows ? absolutePath.Replace(parentPath, ".") : absolutePath.Replace(parentPath.TrimEnd('.'), "./")); #endif }
/// <summary> /// Takes an absolute path and attempts to convert it to a relative, based on the system, /// or global base if no system is supplied, if it is not a subfolder of the base, it will return the path unaltered /// </summary> public static string TryMakeRelative(this PathEntryCollection collection, string absolutePath, string system = null) { var parentPath = string.IsNullOrWhiteSpace(system) ? collection.GlobalBaseAbsolutePath() : collection.AbsolutePathFor(collection.BaseFor(system), system); #if true if (!absolutePath.IsSubfolderOf(parentPath)) { return(absolutePath); } return(OSTailoredCode.IsUnixHost ? "./" + OSTailoredCode.SimpleSubshell("realpath", $"--relative-to=\"{parentPath}\" \"{absolutePath}\"", $"invalid path {absolutePath} or missing realpath binary") : absolutePath.Replace(parentPath, ".")); #else // written for Unix port but may be useful for .NET Core if (!IsSubfolder(parentPath, absolutePath)) { return(OSTailoredCode.IsUnixHost && parentPath.TrimEnd('.') == $"{absolutePath}/" ? "." : absolutePath); } return(OSTailoredCode.IsUnixHost ? absolutePath.Replace(parentPath.TrimEnd('.'), "./") : absolutePath.Replace(parentPath, ".")); #endif }
/// <exception cref="ArgumentException">running on Windows host, and unmanaged call failed</exception> /// <exception cref="FileNotFoundException">running on Windows host, and either path is not a regular file or directory</exception> /// <remarks> /// always returns a relative path, even if it means going up first<br/> /// algorithm for Windows taken from https://stackoverflow.com/a/485516/7467292<br/> /// the parameter names seem backwards, but those are the names used in the Win32 API we're calling /// </remarks> public static string?GetRelativePath(string?fromPath, string?toPath) { if (fromPath == null || toPath == null) { return(null); } if (OSTailoredCode.IsUnixHost) { var realpathOutput = OSTailoredCode.SimpleSubshell("realpath", $"--relative-to=\"{fromPath}\" \"{toPath}\"", $"invalid path {toPath}, invalid path {fromPath}, or missing realpath binary"); return(!realpathOutput.StartsWith("../") && realpathOutput != "." && realpathOutput != ".." ? $"./{realpathOutput}" : realpathOutput); }
/// <remarks>Algorithm for Windows taken from https://stackoverflow.com/a/485516/7467292</remarks> public static string GetRelativePath(string fromPath, string toPath) { if (OSTailoredCode.CurrentOS == OSTailoredCode.DistinctOS.Windows) { Win32.FileAttributes fromAttr = GetPathAttribute(fromPath); Win32.FileAttributes toAttr = GetPathAttribute(toPath); var path = new StringBuilder(260); // MAX_PATH if (Win32.PathRelativePathTo( path, fromPath, fromAttr, toPath, toAttr) == false) { throw new ArgumentException("Paths must have a common prefix"); } return(path.ToString()); } #if true return(PathManager.IsSubfolder(toPath, fromPath) ? "./" + OSTailoredCode.SimpleSubshell("realpath", $"--relative-to=\"{toPath}\" \"{fromPath}\"", $"invalid path {fromPath} or missing realpath binary") : fromPath); #else // written for Unix port but may be useful for .NET Core // algorithm taken from https://stackoverflow.com/a/340454/7467292 var dirSepChar = Path.DirectorySeparatorChar; string from = !fromPath.EndsWith(dirSepChar.ToString()) ? fromPath + dirSepChar : fromPath; string to = !toPath.EndsWith(dirSepChar.ToString()) ? toPath + dirSepChar : toPath; Uri fromUri = new Uri(from); Uri toUri = new Uri(to); if (fromUri.Scheme != toUri.Scheme) { return(toPath); } Uri relativeUri = fromUri.MakeRelativeUri(toUri); string relativePath = Uri.UnescapeDataString(relativeUri.ToString()); if (string.Equals(toUri.Scheme, Uri.UriSchemeFile, StringComparison.OrdinalIgnoreCase)) { relativePath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); } return(relativePath.TrimEnd(dirSepChar)); #endif }
/// <summary> /// Takes an absolute path and attempts to convert it to a relative, based on the system, /// or global base if no system is supplied, if it is not a subfolder of the base, it will return the path unaltered /// </summary> public static string TryMakeRelative(string absolutePath, string system = null) { var parentPath = string.IsNullOrWhiteSpace(system) ? GetGlobalBasePathAbsolute() : MakeAbsolutePath(GetPlatformBase(system), system); if (!IsSubfolder(parentPath, absolutePath)) { return(absolutePath); } return(OSTailoredCode.CurrentOS == OSTailoredCode.DistinctOS.Windows ? absolutePath.Replace(parentPath, ".") : "./" + OSTailoredCode.SimpleSubshell("realpath", $"--relative-to=\"{parentPath}\" \"{absolutePath}\"", $"invalid path {absolutePath} or missing realpath binary")); }
/// <returns><see langword="true"/> iff <paramref name="childPath"/> indicates a child of <paramref name="parentPath"/>, with <see langword="false"/> being returned if either path is <see langword="null"/></returns> /// <remarks>algorithm for Windows taken from https://stackoverflow.com/a/7710620/7467292</remarks> public static bool IsSubfolderOf(this string?childPath, string?parentPath) { if (childPath == null || parentPath == null) { return(false); } if (childPath == parentPath || childPath.StartsWith($"{parentPath}{Path.DirectorySeparatorChar}")) { return(true); } if (OSTailoredCode.IsUnixHost) { #if true var c = OSTailoredCode.SimpleSubshell("realpath", $"-Lm \"{childPath}\"", $"invalid path {childPath} or missing realpath binary"); var p = OSTailoredCode.SimpleSubshell("realpath", $"-Lm \"{parentPath}\"", $"invalid path {parentPath} or missing realpath binary"); return(c == p || c.StartsWith($"{p}/")); #else // written for Unix port but may be useful for Windows when moving to .NET Core var parentUriPath = new Uri(parentPath.TrimEnd('.')).AbsolutePath.TrimEnd('/'); try { for (var childUri = new DirectoryInfo(childPath).Parent; childUri != null; childUri = childUri.Parent) { if (new Uri(childUri.FullName).AbsolutePath.TrimEnd('/') == parentUriPath) { return(true); } } } catch { // ignored } return(false); #endif } var parentUri = new Uri(parentPath.RemoveSuffix(Path.DirectorySeparatorChar)); for (var childUri = new DirectoryInfo(childPath); childUri != null; childUri = childUri.Parent) { if (new Uri(childUri.FullName) == parentUri) { return(true); } } return(false); }
/// <remarks>Algorithm for Windows taken from https://stackoverflow.com/a/7710620/7467292</remarks> public static bool IsSubfolder(string parentPath, string childPath) { if (OSTailoredCode.CurrentOS == OSTailoredCode.DistinctOS.Windows) { var parentUri = new Uri(parentPath); for (var childUri = new DirectoryInfo(childPath).Parent; childUri != null; childUri = childUri?.Parent) { if (new Uri(childUri.FullName) == parentUri) { return(true); } } return(false); } #if true return(OSTailoredCode.SimpleSubshell("realpath", $"-L \"{childPath}\"", $"invalid path {childPath} or missing realpath binary") .StartsWith(OSTailoredCode.SimpleSubshell("realpath", $"-L \"{parentPath}\"", $"invalid path {parentPath} or missing realpath binary"))); #else // written for Unix port but may be useful for .NET Core { var parentUri = new Uri(parentPath.TrimEnd('.')); try { for (var childUri = new DirectoryInfo(childPath).Parent; childUri != null; childUri = childUri?.Parent) { if (new Uri(childUri.FullName).AbsolutePath.TrimEnd('/') == parentUri.AbsolutePath.TrimEnd('/')) { return(true); } } } catch { // ignored } return(false); } #endif }
/// <remarks>Algorithm for Windows taken from https://stackoverflow.com/a/7710620/7467292</remarks> private static bool IsSubfolder(string parentPath, string childPath) { if (OSTailoredCode.CurrentOS == OSTailoredCode.DistinctOS.Windows) { var parentUri = new Uri(parentPath); for (var childUri = new DirectoryInfo(childPath); childUri != null; childUri = childUri?.Parent) { if (new Uri(childUri.FullName) == parentUri) { return(true); } } return(false); } return(OSTailoredCode.SimpleSubshell("realpath", $"-L \"{childPath}\"", $"invalid path {childPath} or missing realpath binary") .StartsWith(OSTailoredCode.SimpleSubshell("realpath", $"-L \"{parentPath}\"", $"invalid path {parentPath} or missing realpath binary"))); }
/// <returns>the relative path which is equivalent to <paramref name="absolutePath"/> when the CWD is <paramref name="basePath"/>, or <see langword="null"/> if either path is <see langword="null"/></returns> /// <remarks>returned string omits trailing slash; implementation calls <see cref="IsSubfolderOf"/> for you</remarks> public static string?MakeRelativeTo(this string?absolutePath, string?basePath) { if (absolutePath == null || basePath == null) { return(null); } if (!absolutePath.IsSubfolderOf(basePath)) { return(absolutePath); } if (!OSTailoredCode.IsUnixHost) { return(absolutePath.Replace(basePath, ".").RemoveSuffix(Path.DirectorySeparatorChar)); } #if true // Unix implementation using realpath var realpathOutput = OSTailoredCode.SimpleSubshell("realpath", $"--relative-to=\"{basePath}\" \"{absolutePath}\"", $"invalid path {absolutePath}, invalid path {basePath}, or missing realpath binary"); return(!realpathOutput.StartsWith("../") && realpathOutput != "." && realpathOutput != ".." ? $"./{realpathOutput}" : realpathOutput); #else // for some reason there were two Unix implementations in the codebase before me? --yoshi // alt. #1 if (!IsSubfolder(basePath, absolutePath)) { return(OSTailoredCode.IsUnixHost && basePath.TrimEnd('.') == $"{absolutePath}/" ? "." : absolutePath); } return(OSTailoredCode.IsUnixHost ? absolutePath.Replace(basePath.TrimEnd('.'), "./") : absolutePath.Replace(basePath, ".")); // alt. #2; algorithm taken from https://stackoverflow.com/a/340454/7467292 var dirSepChar = Path.DirectorySeparatorChar; var fromUri = new Uri(absolutePath.EndsWith(dirSepChar.ToString()) ? absolutePath : absolutePath + dirSepChar); var toUri = new Uri(basePath.EndsWith(dirSepChar.ToString()) ? basePath : basePath + dirSepChar); if (fromUri.Scheme != toUri.Scheme) { return(basePath); } var relativePath = Uri.UnescapeDataString(fromUri.MakeRelativeUri(toUri).ToString()); return((toUri.Scheme.Equals(Uri.UriSchemeFile, StringComparison.OrdinalIgnoreCase) ? relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar) : relativePath ).TrimEnd(dirSepChar)); #endif }