Skip to content

Commit

Permalink
add PathPolyfill
Browse files Browse the repository at this point in the history
  • Loading branch information
SimonCropp committed Jan 12, 2025
1 parent 9dd74b1 commit 8998065
Show file tree
Hide file tree
Showing 5 changed files with 310 additions and 2 deletions.
2 changes: 1 addition & 1 deletion apiCount.include.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
**API count: 441**
**API count: 452**
15 changes: 15 additions & 0 deletions api_list.include.md
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,21 @@
* `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@))


#### PathPolyfill

* `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))))
* `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))))
* `bool EndsInDirectorySeparator(string)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.endsindirectoryseparator#system-io-path-endsindirectoryseparator(system-string))
* `bool Exists(string?)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.exists)
* `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))))
* `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))))
* `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))))
* `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))))
* `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))))
* `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))))
* `string TrimEndingDirectorySeparator(string)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.trimendingdirectoryseparator#system-io-path-trimendingdirectoryseparator(system-string))


#### RegexPolyfill

* `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))
Expand Down
17 changes: 16 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ The package targets `netstandard2.0` and is designed to support the following ru
* `net5.0`, `net6.0`, `net7.0`, `net8.0`, `net9.0`


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


**See [Milestones](../../milestones?state=closed) for release notes.**
Expand Down Expand Up @@ -1011,6 +1011,21 @@ The class `Polyfill` includes the following extension methods:
* `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@))


#### PathPolyfill

* `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))))
* `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))))
* `bool EndsInDirectorySeparator(string)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.endsindirectoryseparator#system-io-path-endsindirectoryseparator(system-string))
* `bool Exists(string?)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.exists)
* `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))))
* `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))))
* `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))))
* `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))))
* `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))))
* `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))))
* `string TrimEndingDirectorySeparator(string)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.trimendingdirectoryseparator#system-io-path-trimendingdirectoryseparator(system-string))


#### RegexPolyfill

* `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))
Expand Down
218 changes: 218 additions & 0 deletions src/Polyfill/PathPolyfill.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
// <auto-generated />

#pragma warning disable

namespace Polyfills;

using System;
using System.IO;
using System.ComponentModel;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;

[ExcludeFromCodeCoverage]
[DebuggerNonUserCode]
#if PolyPublic
public
#endif
static partial class PathPolyfill
{
#if FeatureMemory
/// <summary>
/// Returns the directory information for the specified path represented by a character span.
/// </summary>
/// <param name="path">The path to retrieve the directory information from.</param>
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.io.path.getdirectoryname#system-io-path-getdirectoryname(system-readonlyspan((system-char)))
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER
public static ReadOnlySpan<char> GetDirectoryName(ReadOnlySpan<char> path) =>
Path.GetDirectoryName(path);
#else
public static ReadOnlySpan<char> GetDirectoryName(ReadOnlySpan<char> path) =>
Path.GetDirectoryName(path.ToString()).AsSpan();
#endif

/// <summary>
/// Returns the file name and extension of a file path that is represented by a read-only character span.
/// </summary>
/// <param name="path">A read-only span that contains the path from which to obtain the file name and extension.</param>
/// <returns>The characters after the last directory separator character in path.</returns>
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.io.path.getfilename#system-io-path-getfilename(system-readonlyspan((system-char)))
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER
public static ReadOnlySpan<char> GetFileName(ReadOnlySpan<char> path) =>
Path.GetFileName(path);
#else
public static ReadOnlySpan<char> GetFileName(ReadOnlySpan<char> path) =>
Path.GetFileName(path.ToString()).AsSpan();
#endif

/// <summary>
/// Returns the file name without the extension of a file path that is represented by a read-only character span.
/// </summary>
/// <param name="path">A read-only span that contains the path from which to obtain the file name without the extension.</param>
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.io.path.getfilenamewithoutextension#system-io-path-getfilenamewithoutextension(system-readonlyspan((system-char)))
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER
public static ReadOnlySpan<char> GetFileNameWithoutExtension(ReadOnlySpan<char> path) =>
Path.GetFileNameWithoutExtension(path);
#else
public static ReadOnlySpan<char> GetFileNameWithoutExtension(ReadOnlySpan<char> path) =>
Path.GetFileNameWithoutExtension(path.ToString()).AsSpan();
#endif

/// <summary>
/// Determines whether the path represented by the specified character span includes a file name extension.
/// </summary>
/// <param name="path">The path to search for an extension.</param>
/// <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>
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.io.path.getfilenamewithoutextension#system-io-path-getfilenamewithoutextension(system-readonlyspan((system-char)))
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER
public static bool HasExtension(ReadOnlySpan<char> path) =>
Path.HasExtension(path);
#else
public static bool HasExtension(ReadOnlySpan<char> path) =>
Path.HasExtension(path.ToString());
#endif

/// <summary>
/// Returns the extension of the given path.
/// </summary>
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.io.path.getextension#system-io-path-getextension(system-readonlyspan((system-char)))
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER
public static ReadOnlySpan<char> GetExtension(ReadOnlySpan<char> path) =>
Path.GetExtension(path);
#else
public static ReadOnlySpan<char> GetExtension(ReadOnlySpan<char> path) =>
Path.GetExtension(path.ToString()).AsSpan();
#endif

/// <summary>
/// Combines a span of strings into a path.
/// </summary>
/// <param name="paths">A span of parts of the path.</param>
/// <returns>The combined paths.</returns>
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.io.path.combine#system-io-path-combine(system-readonlyspan((system-string)))
#if NET9_0_OR_GREATER
public static string Combine(scoped ReadOnlySpan<string> paths) =>
Path.Combine(paths);
#else
public static string Combine(scoped ReadOnlySpan<string> paths) =>
Path.Combine(paths.ToArray());
#endif

/// <summary>
/// Returns a value that indicates whether the path, specified as a read-only span, ends in a directory separator.
/// </summary>
/// <param name="path">The path to analyze.</param>
/// <returns>true if the path ends in a directory separator; otherwise, false.</returns>
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.io.path.endsindirectoryseparator#system-io-path-endsindirectoryseparator(system-readonlyspan((system-char)))
#if NETCOREAPP3_0_OR_GREATER
public static bool EndsInDirectorySeparator (ReadOnlySpan<char> path) =>
Path.EndsInDirectorySeparator(path);
#else
public static bool EndsInDirectorySeparator(ReadOnlySpan<char> path) =>
EndsInDirectorySeparator(path.ToString());
#endif


/// <summary>
/// Trims one trailing directory separator beyond the root of the specified path.
/// </summary>
/// <param name="path">The path to trim.</param>
/// <returns>The path without any trailing directory separators.</returns>
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.io.path.trimendingdirectoryseparator#system-io-path-trimendingdirectoryseparator(system-readonlyspan((system-char)))
#if NETCOREAPP3_0_OR_GREATER
public static ReadOnlySpan<char> TrimEndingDirectorySeparator(ReadOnlySpan<char> path) =>
Path.TrimEndingDirectorySeparator(path);
#else
public static ReadOnlySpan<char> TrimEndingDirectorySeparator(ReadOnlySpan<char> path) =>
TrimEndingDirectorySeparator(path.ToString()).AsSpan();
#endif

#endif

/// <summary>
/// Returns a value that indicates whether the specified path ends in a directory separator.
/// </summary>
/// <param name="path">The path to analyze.</param>
/// <returns>true if the path ends in a directory separator; otherwise, false.</returns>
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.io.path.endsindirectoryseparator#system-io-path-endsindirectoryseparator(system-string)
#if NETCOREAPP3_0_OR_GREATER
public static bool EndsInDirectorySeparator(string path) =>
Path.EndsInDirectorySeparator(path);
#else
public static bool EndsInDirectorySeparator(string path)
{
if (string.IsNullOrEmpty(path))
{
return false;
}

return IsDirectorySeparator(path[^1]);
}
#endif

/// <summary>
/// Trims one trailing directory separator beyond the root of the specified path.
/// </summary>
/// <param name="path">The path to trim.</param>
/// <returns>The path without any trailing directory separators.</returns>
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.io.path.trimendingdirectoryseparator#system-io-path-trimendingdirectoryseparator(system-string)
#if NETCOREAPP3_0_OR_GREATER
public static string TrimEndingDirectorySeparator(string path) =>
Path.TrimEndingDirectorySeparator(path);
#else
public static string TrimEndingDirectorySeparator(string path)
{
if (EndsInDirectorySeparator(path) &&
!Path.IsPathRooted(path))
{
return path!.Substring(0, path.Length - 1);
}

return path;
}
#endif

static bool IsDirectorySeparator(char c) =>
c == Path.DirectorySeparatorChar ||
c == Path.AltDirectorySeparatorChar;

/// <summary>
/// Determines whether the specified file or directory exists.
/// </summary>
/// <param name="path">The path to check.</param>
/// <see langword="true" /> if the caller has the required permissions and <paramref name="path" /> contains
/// the name of an existing file or directory; otherwise, <see langword="false" />.
/// This method also returns <see langword="false" /> if <paramref name="path" /> is <see langword="null" />,
/// an invalid path, or a zero-length string. If the caller does not have sufficient permissions to read the specified path,
/// no exception is thrown and the method returns <see langword="false" /> regardless of the existence of <paramref name="path" />.
/// </returns>
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.io.path.exists
#if NET7_0_OR_GREATER
public static bool Exists(string? path) =>
Path.Exists(path);
#else
public static bool Exists(string? path)
{
if (string.IsNullOrEmpty(path))
{
return false;
}

string? fullPath;
try
{
fullPath = Path.GetFullPath(path);
}
catch (Exception ex)
when (ex is ArgumentException or
IOException or
UnauthorizedAccessException)
{
return false;
}

return File.Exists(fullPath) || Directory.Exists(fullPath);
}
#endif
}
60 changes: 60 additions & 0 deletions src/Tests/PathTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
[TestFixture]
public class PathTests
{
[Test]
public void GetDirectoryName() =>
Assert.AreEqual("dir", PathPolyfill.GetDirectoryName("dir/file.txt".AsSpan()).ToString());

[Test]
public void GetFileName() =>
Assert.AreEqual("file.txt", PathPolyfill.GetFileName("dir/file.txt".AsSpan()).ToString());

[Test]
public void GetFileNameWithoutExtension() =>
Assert.AreEqual("file", PathPolyfill.GetFileNameWithoutExtension("dir/file.txt".AsSpan()).ToString());

[Test]
public void HasExtension()
{
Assert.True(PathPolyfill.HasExtension("file.txt".AsSpan()));
Assert.False(PathPolyfill.HasExtension("file".AsSpan()));
}

[Test]
public void GetExtension() =>
Assert.AreEqual(".txt", PathPolyfill.GetExtension("file.txt".AsSpan()).ToString());

[Test]
public void Combine()
{
ReadOnlySpan<string> paths = new[]
{
"folder1",
"folder2",
"file.txt"
};

var result = PathPolyfill.Combine(paths);

Assert.AreEqual("folder1\\folder2\\file.txt", result);
}

[Test]
public void EndsInDirectorySeparator()
{
Assert.False(PathPolyfill.EndsInDirectorySeparator("file.txt".AsSpan()));
Assert.False(PathPolyfill.EndsInDirectorySeparator("file.txt"));
Assert.True(PathPolyfill.EndsInDirectorySeparator("path/".AsSpan()));
Assert.True(PathPolyfill.EndsInDirectorySeparator("path/"));
}

[Test]
public void Exists()
{
Assert.False(PathPolyfill.Exists(null));
Assert.False(PathPolyfill.Exists(""));
Assert.False(PathPolyfill.Exists("file.txt"));
Assert.True(PathPolyfill.Exists(Environment.CurrentDirectory));
Assert.True(PathPolyfill.Exists("Tests.dll"));
}
}

0 comments on commit 8998065

Please sign in to comment.