| | 1 | | namespace Songhay; |
| | 2 | |
|
| | 3 | | /// <summary> |
| | 4 | | /// A few static helper members for <see cref="System.IO"/>. |
| | 5 | | /// </summary> |
| | 6 | | public static partial class ProgramFileUtility |
| | 7 | | { |
| | 8 | | static ProgramFileUtility() |
| 1 | 9 | | { |
| 1 | 10 | | Backslash = '\\'; |
| 1 | 11 | | ForwardSlash = '/'; |
| 1 | 12 | | IsForwardSlashSystemField = Path.DirectorySeparatorChar.Equals(ForwardSlash); |
| 1 | 13 | | TraceSource = TraceSources.Instance.GetConfiguredTraceSource(); |
| 1 | 14 | | } |
| | 15 | |
|
| | 16 | | static readonly TraceSource? TraceSource; |
| | 17 | |
|
| | 18 | | /// <summary> |
| | 19 | | /// Counts the parent directory chars. |
| | 20 | | /// </summary> |
| | 21 | | /// <param name="path">The path.</param> |
| | 22 | | /// <remarks> |
| | 23 | | /// This method is useful when running <see cref="GetParentDirectory(string, int)"/>. |
| | 24 | | /// |
| | 25 | | /// WARNING: call <see cref="NormalizePath(string)"/> to prevent incorrectly returning <c>0</c> |
| | 26 | | /// in cross-platform scenarios. |
| | 27 | | /// </remarks> |
| | 28 | | public static int CountParentDirectoryChars(string? path) |
| 25 | 29 | | { |
| 25 | 30 | | if (string.IsNullOrWhiteSpace(path)) return default; |
| | 31 | |
|
| 25 | 32 | | var parentDirectoryCharsPattern = $@"\.\.\{Path.DirectorySeparatorChar}"; |
| 25 | 33 | | var matches = Regex.Matches(path, parentDirectoryCharsPattern); |
| | 34 | |
|
| 25 | 35 | | return matches.Count; |
| 25 | 36 | | } |
| | 37 | |
|
| | 38 | | /// <summary> |
| | 39 | | /// Finds the parent directory. |
| | 40 | | /// </summary> |
| | 41 | | /// <param name="path">The path.</param> |
| | 42 | | /// <param name="parentName">Name of the parent.</param> |
| | 43 | | /// <param name="levels">The levels.</param> |
| | 44 | | public static string? FindParentDirectory(string? path, string? parentName, int levels) => |
| 0 | 45 | | FindParentDirectoryInfo(path, parentName, levels)?.FullName; |
| | 46 | |
|
| | 47 | | /// <summary> |
| | 48 | | /// Finds the parent <see cref="DirectoryInfo"/>. |
| | 49 | | /// </summary> |
| | 50 | | /// <param name="path">The path.</param> |
| | 51 | | /// <param name="parentName">Name of the parent.</param> |
| | 52 | | /// <param name="levels">The levels.</param> |
| | 53 | | public static DirectoryInfo? FindParentDirectoryInfo(string? path, string? parentName, int levels) |
| 0 | 54 | | { |
| 0 | 55 | | if (string.IsNullOrWhiteSpace(path)) |
| 0 | 56 | | throw new DirectoryNotFoundException("The expected directory is not here."); |
| | 57 | |
|
| 0 | 58 | | var info = new DirectoryInfo(path); |
| | 59 | |
|
| 0 | 60 | | var isParent = (info.Name == parentName); |
| 0 | 61 | | var hasNullParent = (info.Parent == null); |
| 0 | 62 | | var hasTargetParent = !hasNullParent && (info.Parent?.Name == parentName); |
| | 63 | |
|
| 0 | 64 | | if (!info.Exists) return null; |
| 0 | 65 | | if (isParent) return info; |
| 0 | 66 | | if (hasNullParent) return null; |
| 0 | 67 | | if (hasTargetParent) return info.Parent; |
| | 68 | |
|
| 0 | 69 | | levels = Math.Abs(levels); |
| 0 | 70 | | --levels; |
| | 71 | |
|
| 0 | 72 | | var hasNoMoreLevels = (levels == 0); |
| | 73 | |
|
| 0 | 74 | | return hasNoMoreLevels ? null : FindParentDirectoryInfo(info.Parent?.FullName, parentName, levels); |
| 0 | 75 | | } |
| | 76 | |
|
| | 77 | | /// <summary>Combines path and root based |
| | 78 | | /// on the ambient value of <see cref="Path.DirectorySeparatorChar"/> |
| | 79 | | /// of the current OS.</summary> |
| | 80 | | /// <param name="root">The root.</param> |
| | 81 | | /// <param name="path">The path.</param> |
| | 82 | | /// <remarks> |
| | 83 | | /// For detail, see: |
| | 84 | | /// 📚 https://github.com/BryanWilhite/SonghayCore/issues/14 |
| | 85 | | /// 📚 https://github.com/BryanWilhite/SonghayCore/issues/32 |
| | 86 | | /// 📚 https://github.com/BryanWilhite/SonghayCore/issues/97 |
| | 87 | | /// </remarks> |
| | 88 | | public static string GetCombinedPath(string? root, string? path) |
| 74 | 89 | | { |
| 74 | 90 | | root.ThrowWhenNullOrWhiteSpace(); |
| 74 | 91 | | path.ThrowWhenNullOrWhiteSpace(); |
| | 92 | |
|
| 74 | 93 | | path = GetRelativePath(path); |
| | 94 | |
|
| 74 | 95 | | return Path.IsPathRooted(path) |
| 74 | 96 | | ? path |
| 74 | 97 | | : Path.Combine(NormalizePath(root)!, path!); |
| 74 | 98 | | } |
| | 99 | |
|
| | 100 | | /// <summary>Combines path and root based |
| | 101 | | /// on the ambient value of <see cref="Path.DirectorySeparatorChar"/> |
| | 102 | | /// of the current OS.</summary> |
| | 103 | | /// <param name="root">The root.</param> |
| | 104 | | /// <param name="path">The path.</param> |
| | 105 | | /// <param name="fileIsExpected"> |
| | 106 | | /// when <c>true</c> will throw <see cref="FileNotFoundException"/> |
| | 107 | | /// when combined path is not of a file; otherwise |
| | 108 | | /// will throw <see cref="DirectoryNotFoundException"/> |
| | 109 | | /// when combined path is not a directory |
| | 110 | | /// </param> |
| | 111 | | /// <remarks> |
| | 112 | | /// For detail, see: |
| | 113 | | /// 📚 https://github.com/BryanWilhite/SonghayCore/issues/14 |
| | 114 | | /// 📚 https://github.com/BryanWilhite/SonghayCore/issues/32 |
| | 115 | | /// 📚 https://github.com/BryanWilhite/SonghayCore/issues/97 |
| | 116 | | /// </remarks> |
| | 117 | | public static string GetCombinedPath(string? root, string? path, bool fileIsExpected) |
| 6 | 118 | | { |
| 6 | 119 | | var combinedPath = GetCombinedPath(root, path); |
| | 120 | |
|
| 6 | 121 | | if (fileIsExpected) |
| 4 | 122 | | { |
| 4 | 123 | | if (!File.Exists(combinedPath)) |
| 1 | 124 | | throw new FileNotFoundException($"The expected file, `{combinedPath}`, is not here."); |
| 3 | 125 | | } |
| | 126 | | else |
| 2 | 127 | | { |
| 2 | 128 | | if (!Directory.Exists(combinedPath)) |
| 1 | 129 | | throw new DirectoryNotFoundException($"The expected directory, `{combinedPath}`, is not here."); |
| 1 | 130 | | } |
| | 131 | |
|
| 4 | 132 | | return combinedPath; |
| 4 | 133 | | } |
| | 134 | |
|
| | 135 | | /// <summary> |
| | 136 | | /// Gets the parent directory. |
| | 137 | | /// </summary> |
| | 138 | | /// <param name="path">The path.</param> |
| | 139 | | /// <param name="levels">The levels.</param> |
| | 140 | | /// <remarks> |
| | 141 | | /// A recursive wrapper for <see cref="Directory.GetParent(string)"/>. |
| | 142 | | /// </remarks> |
| | 143 | | public static string? GetParentDirectory(string? path, int levels) |
| 84 | 144 | | { |
| 84 | 145 | | path.ThrowWhenNullOrWhiteSpace(); |
| | 146 | |
|
| 84 | 147 | | levels = Math.Abs(levels); |
| 84 | 148 | | if (levels == 0) return path; |
| | 149 | |
|
| 84 | 150 | | var info = Directory.GetParent(path); |
| 84 | 151 | | if (info == null) return path; |
| 84 | 152 | | path = info.FullName; |
| | 153 | |
|
| 84 | 154 | | --levels; |
| | 155 | |
|
| 84 | 156 | | return levels >= 1 ? GetParentDirectory(path, levels) : path; |
| 84 | 157 | | } |
| | 158 | |
|
| | 159 | | /// <summary> |
| | 160 | | /// Gets the parent <see cref="DirectoryInfo"/>. |
| | 161 | | /// </summary> |
| | 162 | | /// <param name="path">The path.</param> |
| | 163 | | /// <param name="levels">The levels.</param> |
| | 164 | | /// <remarks> |
| | 165 | | /// A recursive wrapper for <see cref="Directory.GetParent(string)"/>. |
| | 166 | | /// </remarks> |
| | 167 | | public static DirectoryInfo? GetParentDirectoryInfo(string? path, int levels) |
| 4 | 168 | | { |
| 4 | 169 | | path.ThrowWhenNullOrWhiteSpace(); |
| | 170 | |
|
| 4 | 171 | | var info = new DirectoryInfo(path); |
| | 172 | |
|
| 4 | 173 | | levels = Math.Abs(levels); |
| 4 | 174 | | if (levels == 0) return info; |
| | 175 | |
|
| 4 | 176 | | if (info.Parent == null) return info; |
| 4 | 177 | | path = info.Parent.FullName; |
| | 178 | |
|
| 4 | 179 | | --levels; |
| | 180 | |
|
| 4 | 181 | | return (levels >= 1) ? GetParentDirectoryInfo(path, levels) : info.Parent; |
| 4 | 182 | | } |
| | 183 | |
|
| | 184 | | /// <summary> |
| | 185 | | /// Gets the relative path from the specified file segment |
| | 186 | | /// without leading dots (<c>.</c>) or <see cref="Path.DirectorySeparatorChar" /> chars. |
| | 187 | | /// </summary> |
| | 188 | | /// <param name="fileSegment">The file segment.</param> |
| | 189 | | /// <remarks> |
| | 190 | | /// This method is the equivalent of calling: |
| | 191 | | /// * <see cref="TrimLeadingDirectorySeparatorChars(string)"/> |
| | 192 | | /// * <see cref="NormalizePath(string)"/> |
| | 193 | | /// * <see cref="RemoveBackslashPrefixes(string)"/> |
| | 194 | | /// * <see cref="RemoveForwardslashPrefixes(string)"/> |
| | 195 | | /// </remarks> |
| | 196 | | public static string? GetRelativePath(string? fileSegment) |
| 78 | 197 | | { |
| 78 | 198 | | fileSegment.ThrowWhenNullOrWhiteSpace(); |
| | 199 | |
|
| 78 | 200 | | fileSegment = TrimLeadingDirectorySeparatorChars(fileSegment); |
| 78 | 201 | | fileSegment = NormalizePath(fileSegment); |
| 78 | 202 | | fileSegment = RemoveBackslashPrefixes(fileSegment); |
| 78 | 203 | | fileSegment = RemoveForwardslashPrefixes(fileSegment); |
| | 204 | |
|
| 78 | 205 | | return fileSegment; |
| 78 | 206 | | } |
| | 207 | |
|
| | 208 | | /// <summary> |
| | 209 | | /// Returns <c>true</c> when the current OS |
| | 210 | | /// uses forward-slash (<c>/</c>) paths or not. |
| | 211 | | /// </summary> |
| 3 | 212 | | public static bool IsForwardSlashSystem() => IsForwardSlashSystemField; |
| | 213 | |
|
| | 214 | | /// <summary> |
| | 215 | | /// Normalizes the specified path with respect |
| | 216 | | /// to the ambient value of <see cref="Path.DirectorySeparatorChar"/>. |
| | 217 | | /// </summary> |
| | 218 | | /// <param name="path">The path.</param> |
| | 219 | | public static string? NormalizePath(string? path) |
| 177 | 220 | | { |
| 177 | 221 | | if (string.IsNullOrWhiteSpace(path)) return null; |
| | 222 | |
|
| 177 | 223 | | return IsForwardSlashSystemField |
| 177 | 224 | | ? path.Replace(Backslash, ForwardSlash) |
| 177 | 225 | | : path.Replace(ForwardSlash, Backslash); |
| 177 | 226 | | } |
| | 227 | |
|
| | 228 | | /// <summary> |
| | 229 | | /// Removes conventional Directory prefixes |
| | 230 | | /// for relative paths, e.g. <c>..\</c> or <c>.\</c> |
| | 231 | | /// </summary> |
| | 232 | | /// <param name="path">The path.</param> |
| | 233 | | public static string? RemoveBackslashPrefixes(string? path) => |
| 78 | 234 | | path? |
| 78 | 235 | | .TrimStart(Backslash) |
| 78 | 236 | | .Replace($"..{Backslash}", string.Empty) |
| 78 | 237 | | .Replace($".{Backslash}", string.Empty); |
| | 238 | |
|
| | 239 | | /// <summary> |
| | 240 | | /// Removes conventional Directory prefixes |
| | 241 | | /// for relative paths based on the ambient value\ |
| | 242 | | /// of <see cref="Path.DirectorySeparatorChar"/>. |
| | 243 | | /// </summary> |
| | 244 | | /// <param name="path">The path.</param> |
| | 245 | | public static string? RemoveConventionalPrefixes(string? path) => |
| 0 | 246 | | IsForwardSlashSystemField |
| 0 | 247 | | ? RemoveForwardslashPrefixes(path) |
| 0 | 248 | | : RemoveBackslashPrefixes(path); |
| | 249 | |
|
| | 250 | | /// <summary> |
| | 251 | | /// Removes conventional Directory prefixes |
| | 252 | | /// for relative paths, e.g. <c>../</c> or <c>./</c>. |
| | 253 | | /// </summary> |
| | 254 | | /// <param name="path">The path.</param> |
| | 255 | | public static string? RemoveForwardslashPrefixes(string? path) => |
| 78 | 256 | | path? |
| 78 | 257 | | .TrimStart(ForwardSlash) |
| 78 | 258 | | .Replace($"..{ForwardSlash}", string.Empty) |
| 78 | 259 | | .Replace($".{ForwardSlash}", string.Empty); |
| | 260 | |
|
| | 261 | | /// <summary> |
| | 262 | | /// Trims the leading directory separator chars. |
| | 263 | | /// </summary> |
| | 264 | | /// <param name="path">The path.</param> |
| | 265 | | /// <remarks> |
| | 266 | | /// Trims leading <see cref="Path.AltDirectorySeparatorChar"/> and/or <see cref="Path.DirectorySeparatorChar"/>, |
| | 267 | | /// formatting relative paths for <see cref="Path.Combine(string, string)"/>. |
| | 268 | | /// </remarks> |
| | 269 | | public static string? TrimLeadingDirectorySeparatorChars(string? path) => |
| 104 | 270 | | string.IsNullOrWhiteSpace(path) ? path : path.TrimStart(Backslash, ForwardSlash); |
| | 271 | |
|
| | 272 | | static readonly bool IsForwardSlashSystemField; |
| | 273 | | static readonly char Backslash; |
| | 274 | | static readonly char ForwardSlash; |
| | 275 | | } |