-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathSemanticRelativePath.cs
More file actions
90 lines (78 loc) · 3.15 KB
/
SemanticRelativePath.cs
File metadata and controls
90 lines (78 loc) · 3.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
// Copyright (c) ktsu.dev
// All rights reserved.
// Licensed under the MIT license.
namespace ktsu.Semantics.Paths;
/// <summary>
/// Base class for relative paths (not fully qualified)
/// </summary>
[IsPath, IsRelativePath]
public abstract record SemanticRelativePath<TDerived> : SemanticPath<TDerived>
where TDerived : SemanticRelativePath<TDerived>
{
/// <summary>
/// Creates a relative path from an absolute path to another absolute path
/// </summary>
public static TRelativePath Make<TRelativePath, TFromPath, TToPath>(TFromPath from, TToPath to)
where TRelativePath : SemanticRelativePath<TRelativePath>
where TFromPath : SemanticPath<TFromPath>
where TToPath : SemanticPath<TToPath>
{
#if NET6_0_OR_GREATER
Guard.NotNull(from);
Guard.NotNull(to);
#else
ArgumentNullExceptionPolyfill.ThrowIfNull(from);
ArgumentNullExceptionPolyfill.ThrowIfNull(to);
#endif
FileInfo fromInfo = new(Path.GetFullPath(from.WeakString));
FileInfo toInfo = new(Path.GetFullPath(to.WeakString));
// Use unix-style separators because they work on windows too
const string separator = "/";
const string altSeparator = "\\";
string fromPath = Path.GetFullPath(fromInfo.FullName);
#if NETSTANDARD2_0
fromPath = StringPolyfill.Replace(fromPath, altSeparator, separator, StringComparison.Ordinal);
#else
fromPath = fromPath.Replace(altSeparator, separator, StringComparison.Ordinal);
#endif
string toPath = Path.GetFullPath(toInfo.FullName);
#if NETSTANDARD2_0
toPath = StringPolyfill.Replace(toPath, altSeparator, separator, StringComparison.Ordinal);
#else
toPath = toPath.Replace(altSeparator, separator, StringComparison.Ordinal);
#endif
// Handle directory paths - ensure they end with separator
bool fromIsDirectory = IsDirectoryPath(from);
bool toIsDirectory = IsDirectoryPath(to);
if (fromIsDirectory && !fromPath.EndsWith(separator, StringComparison.Ordinal))
{
fromPath += separator;
}
if (toIsDirectory && !toPath.EndsWith(separator, StringComparison.Ordinal))
{
toPath += separator;
}
Uri fromUri = new(fromPath);
Uri toUri = new(toPath);
Uri relativeUri = fromUri.MakeRelativeUri(toUri);
string relativePath = Uri.UnescapeDataString(relativeUri.ToString());
#if NETSTANDARD2_0
relativePath = StringPolyfill.Replace(relativePath, altSeparator, separator, StringComparison.Ordinal);
#else
relativePath = relativePath.Replace(altSeparator, separator, StringComparison.Ordinal);
#endif
return Create<TRelativePath>(relativePath);
}
/// <summary>
/// Determines whether the specified path type represents a directory path based on its validation attributes.
/// </summary>
/// <typeparam name="T">The type of semantic path to check.</typeparam>
/// <param name="path">The path instance to check.</param>
/// <returns><see langword="true"/> if the path type has the <see cref="IsAbsoluteDirectoryPathAttribute"/>; otherwise, <see langword="false"/>.</returns>
private static bool IsDirectoryPath<T>(T path) where T : SemanticPath<T>
{
// Check if it's a directory-specific type based on validation attributes
Type type = path.GetType();
return type.GetCustomAttributes(typeof(IsAbsoluteDirectoryPathAttribute), true).Length > 0;
}
}