Skip to content

Commit 5539592

Browse files
authored
feat: add missing path APIs (#777)
This adds support for all `System.IO.Path` members in .NET 5 and 6. BREAKING CHANGE: This is a breaking change for people implementing `System.IO.Abstractions.IPath` or deriving from `System.IO.Abstractions.PathBase` because new members have been added to their API surface. Fixes #774
1 parent b802764 commit 5539592

File tree

8 files changed

+224
-50
lines changed

8 files changed

+224
-50
lines changed

Directory.Build.props

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@
1212
<PackageReadmeFile>README.md</PackageReadmeFile>
1313
<DefineConstants Condition="'$(TargetFramework)' != 'net461'">$(DefineConstants);FEATURE_FILE_SYSTEM_ACL_EXTENSIONS</DefineConstants>
1414
<DefineConstants Condition="'$(TargetFramework)' == 'net6.0' OR '$(TargetFramework)' == 'net5.0' OR '$(TargetFramework)' == 'netcoreapp3.1' OR '$(TargetFramework)' == 'netstandard2.1'">$(DefineConstants);FEATURE_ASYNC_FILE;FEATURE_ENUMERATION_OPTIONS;FEATURE_ADVANCED_PATH_OPERATIONS;FEATURE_PATH_JOIN_WITH_SPAN</DefineConstants>
15-
<DefineConstants Condition="'$(TargetFramework)' == 'net6.0' OR '$(TargetFramework)' == 'net5.0'">$(DefineConstants);FEATURE_FILE_MOVE_WITH_OVERWRITE</DefineConstants>
16-
<DefineConstants Condition="'$(TargetFramework)' == 'net6.0' OR '$(TargetFramework)' == 'net5.0'">$(DefineConstants);FEATURE_SUPPORTED_OS_ATTRIBUTE</DefineConstants>
17-
<DefineConstants Condition="'$(TargetFramework)' == 'net6.0' OR '$(TargetFramework)' == 'net5.0'">$(DefineConstants);FEATURE_FILE_SYSTEM_WATCHER_FILTERS</DefineConstants>
15+
<DefineConstants Condition="'$(TargetFramework)' == 'net6.0' OR '$(TargetFramework)' == 'net5.0'">$(DefineConstants);FEATURE_FILE_MOVE_WITH_OVERWRITE;FEATURE_SUPPORTED_OS_ATTRIBUTE;FEATURE_FILE_SYSTEM_WATCHER_FILTERS;FEATURE_ENDS_IN_DIRECTORY_SEPARATOR;FEATURE_PATH_JOIN_WITH_PARAMS;FEATURE_PATH_JOIN_WITH_FOUR_PATHS</DefineConstants>
1816
</PropertyGroup>
1917
<ItemGroup>
2018
<PackageReference Include="Nerdbank.GitVersioning" Version="3.4.244">

src/System.IO.Abstractions/IPath.cs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,5 +79,64 @@ public interface IPath
7979
/// <inheritdoc cref="System.IO.Path.TryJoin(ReadOnlySpan{char}, ReadOnlySpan{char}, ReadOnlySpan{char}, Span{char}, out int)"/>
8080
bool TryJoin(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2, Span<char> destination, out int charsWritten);
8181
#endif
82+
83+
#if FEATURE_ADVANCED_PATH_OPERATIONS
84+
/// <inheritdoc cref="System.IO.Path.HasExtension(ReadOnlySpan{char})"/>
85+
bool HasExtension(ReadOnlySpan<char> path);
86+
87+
/// <inheritdoc cref="System.IO.Path.IsPathFullyQualified(ReadOnlySpan{char})"/>
88+
bool IsPathFullyQualified(ReadOnlySpan<char> path);
89+
90+
/// <inheritdoc cref="System.IO.Path.IsPathRooted(ReadOnlySpan{char})"/>
91+
bool IsPathRooted(ReadOnlySpan<char> path);
92+
93+
/// <inheritdoc cref="System.IO.Path.GetDirectoryName(ReadOnlySpan{char})"/>
94+
ReadOnlySpan<char> GetDirectoryName(ReadOnlySpan<char> path);
95+
96+
/// <inheritdoc cref="System.IO.Path.GetExtension(ReadOnlySpan{char})"/>
97+
ReadOnlySpan<char> GetExtension(ReadOnlySpan<char> path);
98+
99+
/// <inheritdoc cref="System.IO.Path.GetFileName(ReadOnlySpan{char})"/>
100+
ReadOnlySpan<char> GetFileName(ReadOnlySpan<char> path);
101+
102+
/// <inheritdoc cref="System.IO.Path.GetFileNameWithoutExtension(ReadOnlySpan{char})"/>
103+
ReadOnlySpan<char> GetFileNameWithoutExtension(ReadOnlySpan<char> path);
104+
105+
/// <inheritdoc cref="System.IO.Path.GetPathRoot(ReadOnlySpan{char})"/>
106+
ReadOnlySpan<char> GetPathRoot(ReadOnlySpan<char> path);
107+
108+
#endif
109+
110+
#if FEATURE_PATH_JOIN_WITH_PARAMS
111+
/// <inheritdoc cref="System.IO.Path.Join(string,string)" />
112+
string Join(string path1, string path2);
113+
114+
/// <inheritdoc cref="System.IO.Path.Join(string?, string?, string?)" />
115+
string Join(string path1, string path2, string path3);
116+
117+
/// <inheritdoc cref="System.IO.Path.Join(string?[])" />
118+
string Join(params string[] paths);
119+
#endif
120+
121+
#if FEATURE_ENDS_IN_DIRECTORY_SEPARATOR
122+
/// <inheritdoc cref="System.IO.Path.EndsInDirectorySeparator(ReadOnlySpan{char})"/>
123+
bool EndsInDirectorySeparator(ReadOnlySpan<char> path);
124+
125+
/// <inheritdoc cref="System.IO.Path.EndsInDirectorySeparator(string)"/>
126+
bool EndsInDirectorySeparator(string path);
127+
128+
/// <inheritdoc cref="System.IO.Path.TrimEndingDirectorySeparator(ReadOnlySpan{char})"/>
129+
ReadOnlySpan<char> TrimEndingDirectorySeparator(ReadOnlySpan<char> path);
130+
/// <inheritdoc cref="System.IO.Path.TrimEndingDirectorySeparator(string)" />
131+
string TrimEndingDirectorySeparator(string path);
132+
#endif
133+
134+
#if FEATURE_PATH_JOIN_WITH_FOUR_PATHS
135+
/// <inheritdoc cref="System.IO.Path.Join(ReadOnlySpan{char}, ReadOnlySpan{char}, ReadOnlySpan{char}, ReadOnlySpan{char})" />
136+
string Join(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2, ReadOnlySpan<char> path3, ReadOnlySpan<char> path4);
137+
138+
/// <inheritdoc cref="System.IO.Path.Join(string?, string?, string?, string?)" />
139+
string Join(string path1, string path2, string path3, string path4);
140+
#endif
82141
}
83142
}

src/System.IO.Abstractions/PathBase.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,5 +114,56 @@ internal PathBase() { }
114114
/// <inheritdoc />
115115
public abstract bool TryJoin(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2, Span<char> destination, out int charsWritten);
116116
#endif
117+
118+
#if FEATURE_ADVANCED_PATH_OPERATIONS
119+
/// <inheritdoc />
120+
public abstract bool HasExtension(ReadOnlySpan<char> path);
121+
/// <inheritdoc />
122+
public abstract bool IsPathFullyQualified(ReadOnlySpan<char> path);
123+
/// <inheritdoc />
124+
public abstract bool IsPathRooted(ReadOnlySpan<char> path);
125+
/// <inheritdoc />
126+
public abstract ReadOnlySpan<char> GetDirectoryName(ReadOnlySpan<char> path);
127+
/// <inheritdoc />
128+
public abstract ReadOnlySpan<char> GetExtension(ReadOnlySpan<char> path);
129+
/// <inheritdoc />
130+
public abstract ReadOnlySpan<char> GetFileName(ReadOnlySpan<char> path);
131+
/// <inheritdoc />
132+
public abstract ReadOnlySpan<char> GetFileNameWithoutExtension(ReadOnlySpan<char> path);
133+
/// <inheritdoc />
134+
public abstract ReadOnlySpan<char> GetPathRoot(ReadOnlySpan<char> path);
135+
136+
#endif
137+
#if FEATURE_PATH_JOIN_WITH_PARAMS
138+
/// <inheritdoc />
139+
public abstract string Join(params string[] paths);
140+
141+
/// <inheritdoc />
142+
public abstract string Join(string path1, string path2);
143+
/// <inheritdoc />
144+
145+
public abstract string Join(string path1, string path2, string path3);
146+
147+
#endif
148+
149+
#if FEATURE_ENDS_IN_DIRECTORY_SEPARATOR
150+
/// <inheritdoc />
151+
public abstract bool EndsInDirectorySeparator(ReadOnlySpan<char> path);
152+
/// <inheritdoc />
153+
public abstract bool EndsInDirectorySeparator(string path);
154+
/// <inheritdoc />
155+
public abstract ReadOnlySpan<char> TrimEndingDirectorySeparator(ReadOnlySpan<char> path);
156+
157+
/// <inheritdoc />
158+
public abstract string TrimEndingDirectorySeparator(string path);
159+
#endif
160+
161+
#if FEATURE_PATH_JOIN_WITH_FOUR_PATHS
162+
163+
/// <inheritdoc />
164+
public abstract string Join(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2, ReadOnlySpan<char> path3, ReadOnlySpan<char> path4);
165+
/// <inheritdoc />
166+
public abstract string Join(string path1, string path2, string path3, string path4);
167+
#endif
117168
}
118169
}

src/System.IO.Abstractions/PathWrapper.cs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,5 +187,115 @@ public override bool IsPathRooted(string path)
187187
{
188188
return Path.IsPathRooted(path);
189189
}
190+
191+
#if FEATURE_ENDS_IN_DIRECTORY_SEPARATOR
192+
/// <inheritdoc />
193+
public override bool EndsInDirectorySeparator(ReadOnlySpan<char> path)
194+
{
195+
return Path.EndsInDirectorySeparator(path);
196+
}
197+
198+
/// <inheritdoc />
199+
public override bool EndsInDirectorySeparator(string path)
200+
{
201+
return Path.EndsInDirectorySeparator(path);
202+
}
203+
204+
/// <inheritdoc />
205+
public override ReadOnlySpan<char> TrimEndingDirectorySeparator(ReadOnlySpan<char> path)
206+
{
207+
return Path.TrimEndingDirectorySeparator(path);
208+
}
209+
210+
/// <inheritdoc />
211+
public override string TrimEndingDirectorySeparator(string path)
212+
{
213+
return Path.TrimEndingDirectorySeparator(path);
214+
}
215+
#endif
216+
217+
#if FEATURE_ADVANCED_PATH_OPERATIONS
218+
/// <inheritdoc />
219+
public override bool HasExtension(ReadOnlySpan<char> path)
220+
{
221+
return Path.HasExtension(path);
222+
}
223+
224+
/// <inheritdoc />
225+
public override bool IsPathFullyQualified(ReadOnlySpan<char> path)
226+
{
227+
return Path.IsPathFullyQualified(path);
228+
}
229+
230+
/// <inheritdoc />
231+
public override bool IsPathRooted(ReadOnlySpan<char> path)
232+
{
233+
return Path.IsPathRooted(path);
234+
}
235+
236+
/// <inheritdoc />
237+
public override ReadOnlySpan<char> GetDirectoryName(ReadOnlySpan<char> path)
238+
{
239+
return Path.GetDirectoryName(path);
240+
}
241+
242+
/// <inheritdoc />
243+
public override ReadOnlySpan<char> GetExtension(ReadOnlySpan<char> path)
244+
{
245+
return Path.GetExtension(path);
246+
}
247+
248+
/// <inheritdoc />
249+
public override ReadOnlySpan<char> GetFileName(ReadOnlySpan<char> path)
250+
{
251+
return Path.GetFileName(path);
252+
}
253+
254+
/// <inheritdoc />
255+
public override ReadOnlySpan<char> GetFileNameWithoutExtension(ReadOnlySpan<char> path)
256+
{
257+
return Path.GetFileNameWithoutExtension(path);
258+
}
259+
260+
/// <inheritdoc />
261+
public override ReadOnlySpan<char> GetPathRoot(ReadOnlySpan<char> path)
262+
{
263+
return Path.GetPathRoot(path);
264+
}
265+
#endif
266+
267+
#if FEATURE_PATH_JOIN_WITH_FOUR_PATHS
268+
/// <inheritdoc />
269+
public override string Join(string path1, string path2, string path3, string path4)
270+
{
271+
return Path.Join(path1, path2, path3, path4);
272+
}
273+
274+
/// <inheritdoc />
275+
public override string Join(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2, ReadOnlySpan<char> path3, ReadOnlySpan<char> path4)
276+
{
277+
return Path.Join(path1, path2, path3, path4);
278+
}
279+
#endif
280+
281+
#if FEATURE_PATH_JOIN_WITH_PARAMS
282+
/// <inheritdoc />
283+
public override string Join(string path1, string path2)
284+
{
285+
return Path.Join(path1, path2);
286+
}
287+
288+
/// <inheritdoc />
289+
public override string Join(string path1, string path2, string path3)
290+
{
291+
return Path.Join(path1, path2, path3);
292+
}
293+
294+
/// <inheritdoc />
295+
public override string Join(params string[] paths)
296+
{
297+
return Path.Join(paths);
298+
}
299+
#endif
190300
}
191301
}

tests/System.IO.Abstractions.Tests/__snapshots__/ApiParityTests.Path_.NET 5.0.snap

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,5 @@
66
"Char get_VolumeSeparatorChar()",
77
"Char[] get_InvalidPathChars()"
88
],
9-
"MissingMembers": [
10-
"Boolean EndsInDirectorySeparator(System.ReadOnlySpan`1[System.Char])",
11-
"Boolean EndsInDirectorySeparator(System.String)",
12-
"Boolean HasExtension(System.ReadOnlySpan`1[System.Char])",
13-
"Boolean IsPathFullyQualified(System.ReadOnlySpan`1[System.Char])",
14-
"Boolean IsPathRooted(System.ReadOnlySpan`1[System.Char])",
15-
"System.ReadOnlySpan`1[System.Char] GetDirectoryName(System.ReadOnlySpan`1[System.Char])",
16-
"System.ReadOnlySpan`1[System.Char] GetExtension(System.ReadOnlySpan`1[System.Char])",
17-
"System.ReadOnlySpan`1[System.Char] GetFileName(System.ReadOnlySpan`1[System.Char])",
18-
"System.ReadOnlySpan`1[System.Char] GetFileNameWithoutExtension(System.ReadOnlySpan`1[System.Char])",
19-
"System.ReadOnlySpan`1[System.Char] GetPathRoot(System.ReadOnlySpan`1[System.Char])",
20-
"System.ReadOnlySpan`1[System.Char] TrimEndingDirectorySeparator(System.ReadOnlySpan`1[System.Char])",
21-
"System.String Join(System.ReadOnlySpan`1[System.Char], System.ReadOnlySpan`1[System.Char], System.ReadOnlySpan`1[System.Char], System.ReadOnlySpan`1[System.Char])",
22-
"System.String Join(System.String, System.String)",
23-
"System.String Join(System.String, System.String, System.String)",
24-
"System.String Join(System.String, System.String, System.String, System.String)",
25-
"System.String Join(System.String[])",
26-
"System.String TrimEndingDirectorySeparator(System.String)"
27-
]
9+
"MissingMembers": []
2810
}

tests/System.IO.Abstractions.Tests/__snapshots__/ApiParityTests.Path_.NET 6.0.snap

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,5 @@
66
"Char get_VolumeSeparatorChar()",
77
"Char[] get_InvalidPathChars()"
88
],
9-
"MissingMembers": [
10-
"Boolean EndsInDirectorySeparator(System.ReadOnlySpan`1[System.Char])",
11-
"Boolean EndsInDirectorySeparator(System.String)",
12-
"Boolean HasExtension(System.ReadOnlySpan`1[System.Char])",
13-
"Boolean IsPathFullyQualified(System.ReadOnlySpan`1[System.Char])",
14-
"Boolean IsPathRooted(System.ReadOnlySpan`1[System.Char])",
15-
"System.ReadOnlySpan`1[System.Char] GetDirectoryName(System.ReadOnlySpan`1[System.Char])",
16-
"System.ReadOnlySpan`1[System.Char] GetExtension(System.ReadOnlySpan`1[System.Char])",
17-
"System.ReadOnlySpan`1[System.Char] GetFileName(System.ReadOnlySpan`1[System.Char])",
18-
"System.ReadOnlySpan`1[System.Char] GetFileNameWithoutExtension(System.ReadOnlySpan`1[System.Char])",
19-
"System.ReadOnlySpan`1[System.Char] GetPathRoot(System.ReadOnlySpan`1[System.Char])",
20-
"System.ReadOnlySpan`1[System.Char] TrimEndingDirectorySeparator(System.ReadOnlySpan`1[System.Char])",
21-
"System.String Join(System.ReadOnlySpan`1[System.Char], System.ReadOnlySpan`1[System.Char], System.ReadOnlySpan`1[System.Char], System.ReadOnlySpan`1[System.Char])",
22-
"System.String Join(System.String, System.String)",
23-
"System.String Join(System.String, System.String, System.String)",
24-
"System.String Join(System.String, System.String, System.String, System.String)",
25-
"System.String Join(System.String[])",
26-
"System.String TrimEndingDirectorySeparator(System.String)"
27-
]
9+
"MissingMembers": []
2810
}

tests/System.IO.Abstractions.Tests/__snapshots__/ApiParityTests.Path_.NET Core 3.1.snap

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,6 @@
99
"MissingMembers": [
1010
"Boolean EndsInDirectorySeparator(System.ReadOnlySpan`1[System.Char])",
1111
"Boolean EndsInDirectorySeparator(System.String)",
12-
"Boolean HasExtension(System.ReadOnlySpan`1[System.Char])",
13-
"Boolean IsPathFullyQualified(System.ReadOnlySpan`1[System.Char])",
14-
"Boolean IsPathRooted(System.ReadOnlySpan`1[System.Char])",
15-
"System.ReadOnlySpan`1[System.Char] GetDirectoryName(System.ReadOnlySpan`1[System.Char])",
16-
"System.ReadOnlySpan`1[System.Char] GetExtension(System.ReadOnlySpan`1[System.Char])",
17-
"System.ReadOnlySpan`1[System.Char] GetFileName(System.ReadOnlySpan`1[System.Char])",
18-
"System.ReadOnlySpan`1[System.Char] GetFileNameWithoutExtension(System.ReadOnlySpan`1[System.Char])",
19-
"System.ReadOnlySpan`1[System.Char] GetPathRoot(System.ReadOnlySpan`1[System.Char])",
2012
"System.ReadOnlySpan`1[System.Char] TrimEndingDirectorySeparator(System.ReadOnlySpan`1[System.Char])",
2113
"System.String Join(System.ReadOnlySpan`1[System.Char], System.ReadOnlySpan`1[System.Char], System.ReadOnlySpan`1[System.Char], System.ReadOnlySpan`1[System.Char])",
2214
"System.String Join(System.String, System.String)",

version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
3-
"version": "14.0",
3+
"version": "15.0",
44
"assemblyVersion": {
55
"precision": "major"
66
},

0 commit comments

Comments
 (0)