Skip to content

Commit 1956dab

Browse files
authored
add PathPolyfill (#278)
1 parent 9dd74b1 commit 1956dab

File tree

6 files changed

+314
-3
lines changed

6 files changed

+314
-3
lines changed

apiCount.include.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
**API count: 441**
1+
**API count: 452**

api_list.include.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,21 @@
542542
* `bool TryParse(string?, IFormatProvider?, long)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.int64.tryparse#system-int64-tryparse(system-string-system-iformatprovider-system-int64@))
543543

544544

545+
#### PathPolyfill
546+
547+
* `string Combine(ReadOnlySpan<string>)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.combine#system-io-path-combine(system-readonlyspan((system-string))))
548+
* `bool EndsInDirectorySeparator(ReadOnlySpan<char>)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.endsindirectoryseparator#system-io-path-endsindirectoryseparator(system-readonlyspan((system-char))))
549+
* `bool EndsInDirectorySeparator(string)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.endsindirectoryseparator#system-io-path-endsindirectoryseparator(system-string))
550+
* `bool Exists(string?)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.exists)
551+
* `ReadOnlySpan<char> GetDirectoryName(ReadOnlySpan<char>)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.getdirectoryname#system-io-path-getdirectoryname(system-readonlyspan((system-char))))
552+
* `ReadOnlySpan<char> GetExtension(ReadOnlySpan<char>)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.getextension#system-io-path-getextension(system-readonlyspan((system-char))))
553+
* `ReadOnlySpan<char> GetFileName(ReadOnlySpan<char>)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.getfilename#system-io-path-getfilename(system-readonlyspan((system-char))))
554+
* `ReadOnlySpan<char> GetFileNameWithoutExtension(ReadOnlySpan<char>)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.getfilenamewithoutextension#system-io-path-getfilenamewithoutextension(system-readonlyspan((system-char))))
555+
* `bool HasExtension(ReadOnlySpan<char>)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.getfilenamewithoutextension#system-io-path-getfilenamewithoutextension(system-readonlyspan((system-char))))
556+
* `ReadOnlySpan<char> TrimEndingDirectorySeparator(ReadOnlySpan<char>)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.trimendingdirectoryseparator#system-io-path-trimendingdirectoryseparator(system-readonlyspan((system-char))))
557+
* `string TrimEndingDirectorySeparator(string)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.trimendingdirectoryseparator#system-io-path-trimendingdirectoryseparator(system-string))
558+
559+
545560
#### RegexPolyfill
546561

547562
* `ValueMatchEnumerator EnumerateMatches(ReadOnlySpan<char>, string, RegexOptions, TimeSpan)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.enumeratematches#system-text-regularexpressions-regex-enumeratematches(system-readonlyspan((system-char))-system-string-system-text-regularexpressions-regexoptions-system-timespan))

readme.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ The package targets `netstandard2.0` and is designed to support the following ru
1212
* `net5.0`, `net6.0`, `net7.0`, `net8.0`, `net9.0`
1313

1414

15-
**API count: 441**<!-- singleLineInclude: apiCount. path: /apiCount.include.md -->
15+
**API count: 452**<!-- singleLineInclude: apiCount. path: /apiCount.include.md -->
1616

1717

1818
**See [Milestones](../../milestones?state=closed) for release notes.**
@@ -1011,6 +1011,21 @@ The class `Polyfill` includes the following extension methods:
10111011
* `bool TryParse(string?, IFormatProvider?, long)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.int64.tryparse#system-int64-tryparse(system-string-system-iformatprovider-system-int64@))
10121012

10131013

1014+
#### PathPolyfill
1015+
1016+
* `string Combine(ReadOnlySpan<string>)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.combine#system-io-path-combine(system-readonlyspan((system-string))))
1017+
* `bool EndsInDirectorySeparator(ReadOnlySpan<char>)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.endsindirectoryseparator#system-io-path-endsindirectoryseparator(system-readonlyspan((system-char))))
1018+
* `bool EndsInDirectorySeparator(string)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.endsindirectoryseparator#system-io-path-endsindirectoryseparator(system-string))
1019+
* `bool Exists(string?)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.exists)
1020+
* `ReadOnlySpan<char> GetDirectoryName(ReadOnlySpan<char>)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.getdirectoryname#system-io-path-getdirectoryname(system-readonlyspan((system-char))))
1021+
* `ReadOnlySpan<char> GetExtension(ReadOnlySpan<char>)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.getextension#system-io-path-getextension(system-readonlyspan((system-char))))
1022+
* `ReadOnlySpan<char> GetFileName(ReadOnlySpan<char>)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.getfilename#system-io-path-getfilename(system-readonlyspan((system-char))))
1023+
* `ReadOnlySpan<char> GetFileNameWithoutExtension(ReadOnlySpan<char>)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.getfilenamewithoutextension#system-io-path-getfilenamewithoutextension(system-readonlyspan((system-char))))
1024+
* `bool HasExtension(ReadOnlySpan<char>)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.getfilenamewithoutextension#system-io-path-getfilenamewithoutextension(system-readonlyspan((system-char))))
1025+
* `ReadOnlySpan<char> TrimEndingDirectorySeparator(ReadOnlySpan<char>)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.trimendingdirectoryseparator#system-io-path-trimendingdirectoryseparator(system-readonlyspan((system-char))))
1026+
* `string TrimEndingDirectorySeparator(string)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.trimendingdirectoryseparator#system-io-path-trimendingdirectoryseparator(system-string))
1027+
1028+
10141029
#### RegexPolyfill
10151030

10161031
* `ValueMatchEnumerator EnumerateMatches(ReadOnlySpan<char>, string, RegexOptions, TimeSpan)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.enumeratematches#system-text-regularexpressions-regex-enumeratematches(system-readonlyspan((system-char))-system-string-system-text-regularexpressions-regexoptions-system-timespan))

src/Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Project>
33
<PropertyGroup>
4-
<Version>7.11.0</Version>
4+
<Version>7.12.0</Version>
55
<AssemblyVersion>1.0.0</AssemblyVersion>
66
<PackageTags>Polyfill</PackageTags>
77
<DisableImplicitNamespaceImports>true</DisableImplicitNamespaceImports>

src/Polyfill/PathPolyfill.cs

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
// <auto-generated />
2+
3+
#pragma warning disable
4+
5+
namespace Polyfills;
6+
7+
using System;
8+
using System.IO;
9+
using System.ComponentModel;
10+
using System.Collections.Generic;
11+
using System.Diagnostics;
12+
using System.Diagnostics.CodeAnalysis;
13+
14+
[ExcludeFromCodeCoverage]
15+
[DebuggerNonUserCode]
16+
#if PolyPublic
17+
public
18+
#endif
19+
static partial class PathPolyfill
20+
{
21+
#if FeatureMemory
22+
/// <summary>
23+
/// Returns the directory information for the specified path represented by a character span.
24+
/// </summary>
25+
/// <param name="path">The path to retrieve the directory information from.</param>
26+
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.io.path.getdirectoryname#system-io-path-getdirectoryname(system-readonlyspan((system-char)))
27+
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER
28+
public static ReadOnlySpan<char> GetDirectoryName(ReadOnlySpan<char> path) =>
29+
Path.GetDirectoryName(path);
30+
#else
31+
public static ReadOnlySpan<char> GetDirectoryName(ReadOnlySpan<char> path) =>
32+
Path.GetDirectoryName(path.ToString()).AsSpan();
33+
#endif
34+
35+
/// <summary>
36+
/// Returns the file name and extension of a file path that is represented by a read-only character span.
37+
/// </summary>
38+
/// <param name="path">A read-only span that contains the path from which to obtain the file name and extension.</param>
39+
/// <returns>The characters after the last directory separator character in path.</returns>
40+
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.io.path.getfilename#system-io-path-getfilename(system-readonlyspan((system-char)))
41+
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER
42+
public static ReadOnlySpan<char> GetFileName(ReadOnlySpan<char> path) =>
43+
Path.GetFileName(path);
44+
#else
45+
public static ReadOnlySpan<char> GetFileName(ReadOnlySpan<char> path) =>
46+
Path.GetFileName(path.ToString()).AsSpan();
47+
#endif
48+
49+
/// <summary>
50+
/// Returns the file name without the extension of a file path that is represented by a read-only character span.
51+
/// </summary>
52+
/// <param name="path">A read-only span that contains the path from which to obtain the file name without the extension.</param>
53+
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.io.path.getfilenamewithoutextension#system-io-path-getfilenamewithoutextension(system-readonlyspan((system-char)))
54+
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER
55+
public static ReadOnlySpan<char> GetFileNameWithoutExtension(ReadOnlySpan<char> path) =>
56+
Path.GetFileNameWithoutExtension(path);
57+
#else
58+
public static ReadOnlySpan<char> GetFileNameWithoutExtension(ReadOnlySpan<char> path) =>
59+
Path.GetFileNameWithoutExtension(path.ToString()).AsSpan();
60+
#endif
61+
62+
/// <summary>
63+
/// Determines whether the path represented by the specified character span includes a file name extension.
64+
/// </summary>
65+
/// <param name="path">The path to search for an extension.</param>
66+
/// <returns>true if the characters that follow the last directory separator character or volume separator in the path include a period (".") followed by one or more characters; otherwise, false.</returns>
67+
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.io.path.getfilenamewithoutextension#system-io-path-getfilenamewithoutextension(system-readonlyspan((system-char)))
68+
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER
69+
public static bool HasExtension(ReadOnlySpan<char> path) =>
70+
Path.HasExtension(path);
71+
#else
72+
public static bool HasExtension(ReadOnlySpan<char> path) =>
73+
Path.HasExtension(path.ToString());
74+
#endif
75+
76+
/// <summary>
77+
/// Returns the extension of the given path.
78+
/// </summary>
79+
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.io.path.getextension#system-io-path-getextension(system-readonlyspan((system-char)))
80+
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER
81+
public static ReadOnlySpan<char> GetExtension(ReadOnlySpan<char> path) =>
82+
Path.GetExtension(path);
83+
#else
84+
public static ReadOnlySpan<char> GetExtension(ReadOnlySpan<char> path) =>
85+
Path.GetExtension(path.ToString()).AsSpan();
86+
#endif
87+
88+
/// <summary>
89+
/// Combines a span of strings into a path.
90+
/// </summary>
91+
/// <param name="paths">A span of parts of the path.</param>
92+
/// <returns>The combined paths.</returns>
93+
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.io.path.combine#system-io-path-combine(system-readonlyspan((system-string)))
94+
#if NET9_0_OR_GREATER
95+
public static string Combine(scoped ReadOnlySpan<string> paths) =>
96+
Path.Combine(paths);
97+
#else
98+
public static string Combine(scoped ReadOnlySpan<string> paths) =>
99+
Path.Combine(paths.ToArray());
100+
#endif
101+
102+
/// <summary>
103+
/// Returns a value that indicates whether the path, specified as a read-only span, ends in a directory separator.
104+
/// </summary>
105+
/// <param name="path">The path to analyze.</param>
106+
/// <returns>true if the path ends in a directory separator; otherwise, false.</returns>
107+
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.io.path.endsindirectoryseparator#system-io-path-endsindirectoryseparator(system-readonlyspan((system-char)))
108+
#if NETCOREAPP3_0_OR_GREATER
109+
public static bool EndsInDirectorySeparator (ReadOnlySpan<char> path) =>
110+
Path.EndsInDirectorySeparator(path);
111+
#else
112+
public static bool EndsInDirectorySeparator(ReadOnlySpan<char> path) =>
113+
EndsInDirectorySeparator(path.ToString());
114+
#endif
115+
116+
117+
/// <summary>
118+
/// Trims one trailing directory separator beyond the root of the specified path.
119+
/// </summary>
120+
/// <param name="path">The path to trim.</param>
121+
/// <returns>The path without any trailing directory separators.</returns>
122+
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.io.path.trimendingdirectoryseparator#system-io-path-trimendingdirectoryseparator(system-readonlyspan((system-char)))
123+
#if NETCOREAPP3_0_OR_GREATER
124+
public static ReadOnlySpan<char> TrimEndingDirectorySeparator(ReadOnlySpan<char> path) =>
125+
Path.TrimEndingDirectorySeparator(path);
126+
#else
127+
public static ReadOnlySpan<char> TrimEndingDirectorySeparator(ReadOnlySpan<char> path) =>
128+
TrimEndingDirectorySeparator(path.ToString()).AsSpan();
129+
#endif
130+
131+
#endif
132+
133+
/// <summary>
134+
/// Returns a value that indicates whether the specified path ends in a directory separator.
135+
/// </summary>
136+
/// <param name="path">The path to analyze.</param>
137+
/// <returns>true if the path ends in a directory separator; otherwise, false.</returns>
138+
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.io.path.endsindirectoryseparator#system-io-path-endsindirectoryseparator(system-string)
139+
#if NETCOREAPP3_0_OR_GREATER
140+
public static bool EndsInDirectorySeparator(string path) =>
141+
Path.EndsInDirectorySeparator(path);
142+
#else
143+
public static bool EndsInDirectorySeparator(string path)
144+
{
145+
if (string.IsNullOrEmpty(path))
146+
{
147+
return false;
148+
}
149+
150+
return IsDirectorySeparator(path[path.Length-1]);
151+
}
152+
#endif
153+
154+
/// <summary>
155+
/// Trims one trailing directory separator beyond the root of the specified path.
156+
/// </summary>
157+
/// <param name="path">The path to trim.</param>
158+
/// <returns>The path without any trailing directory separators.</returns>
159+
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.io.path.trimendingdirectoryseparator#system-io-path-trimendingdirectoryseparator(system-string)
160+
#if NETCOREAPP3_0_OR_GREATER
161+
public static string TrimEndingDirectorySeparator(string path) =>
162+
Path.TrimEndingDirectorySeparator(path);
163+
#else
164+
public static string TrimEndingDirectorySeparator(string path)
165+
{
166+
if (EndsInDirectorySeparator(path) &&
167+
!Path.IsPathRooted(path))
168+
{
169+
return path!.Substring(0, path.Length - 1);
170+
}
171+
172+
return path;
173+
}
174+
#endif
175+
176+
static bool IsDirectorySeparator(char c) =>
177+
c == Path.DirectorySeparatorChar ||
178+
c == Path.AltDirectorySeparatorChar;
179+
180+
/// <summary>
181+
/// Determines whether the specified file or directory exists.
182+
/// </summary>
183+
/// <param name="path">The path to check.</param>
184+
/// <see langword="true" /> if the caller has the required permissions and <paramref name="path" /> contains
185+
/// the name of an existing file or directory; otherwise, <see langword="false" />.
186+
/// This method also returns <see langword="false" /> if <paramref name="path" /> is <see langword="null" />,
187+
/// an invalid path, or a zero-length string. If the caller does not have sufficient permissions to read the specified path,
188+
/// no exception is thrown and the method returns <see langword="false" /> regardless of the existence of <paramref name="path" />.
189+
/// </returns>
190+
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.io.path.exists
191+
#if NET7_0_OR_GREATER
192+
public static bool Exists(string? path) =>
193+
Path.Exists(path);
194+
#else
195+
public static bool Exists(string? path)
196+
{
197+
if (string.IsNullOrEmpty(path))
198+
{
199+
return false;
200+
}
201+
202+
string? fullPath;
203+
try
204+
{
205+
fullPath = Path.GetFullPath(path);
206+
}
207+
catch (Exception ex)
208+
when (ex is ArgumentException or
209+
IOException or
210+
UnauthorizedAccessException)
211+
{
212+
return false;
213+
}
214+
215+
return File.Exists(fullPath) || Directory.Exists(fullPath);
216+
}
217+
#endif
218+
}

src/Tests/PathTests.cs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
[TestFixture]
2+
public class PathTests
3+
{
4+
#if FeatureMemory
5+
[Test]
6+
public void GetDirectoryName() =>
7+
Assert.AreEqual("dir", PathPolyfill.GetDirectoryName("dir/file.txt".AsSpan()).ToString());
8+
9+
[Test]
10+
public void GetFileName() =>
11+
Assert.AreEqual("file.txt", PathPolyfill.GetFileName("dir/file.txt".AsSpan()).ToString());
12+
13+
[Test]
14+
public void GetFileNameWithoutExtension() =>
15+
Assert.AreEqual("file", PathPolyfill.GetFileNameWithoutExtension("dir/file.txt".AsSpan()).ToString());
16+
17+
[Test]
18+
public void HasExtension()
19+
{
20+
Assert.True(PathPolyfill.HasExtension("file.txt".AsSpan()));
21+
Assert.False(PathPolyfill.HasExtension("file".AsSpan()));
22+
}
23+
24+
[Test]
25+
public void GetExtension() =>
26+
Assert.AreEqual(".txt", PathPolyfill.GetExtension("file.txt".AsSpan()).ToString());
27+
28+
[Test]
29+
public void Combine()
30+
{
31+
ReadOnlySpan<string> paths =
32+
[
33+
"folder1",
34+
"folder2",
35+
"file.txt"
36+
];
37+
38+
var result = PathPolyfill.Combine(paths);
39+
40+
Assert.AreEqual("folder1\\folder2\\file.txt", result);
41+
}
42+
#endif
43+
44+
[Test]
45+
public void EndsInDirectorySeparator()
46+
{
47+
#if FeatureMemory
48+
Assert.False(PathPolyfill.EndsInDirectorySeparator("file.txt".AsSpan()));
49+
Assert.True(PathPolyfill.EndsInDirectorySeparator("path/".AsSpan()));
50+
#endif
51+
Assert.False(PathPolyfill.EndsInDirectorySeparator("file.txt"));
52+
Assert.True(PathPolyfill.EndsInDirectorySeparator("path/"));
53+
}
54+
55+
[Test]
56+
public void Exists()
57+
{
58+
Assert.False(PathPolyfill.Exists(null));
59+
Assert.False(PathPolyfill.Exists(""));
60+
Assert.False(PathPolyfill.Exists("file.txt"));
61+
Assert.True(PathPolyfill.Exists(Environment.CurrentDirectory));
62+
}
63+
}

0 commit comments

Comments
 (0)