Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
af5b1ba
Expand GetDirectoryName test coverage
rameel Aug 10, 2025
ac05158
Mark the NotFoundDirectory class as sealed
rameel Aug 10, 2025
e64c587
Throw UnauthorizedAccess when writing to non-existent file or directory
rameel Aug 10, 2025
6f1588e
Clean up redundant using directives
rameel Aug 10, 2025
b76844c
Clean up and speed up VirtualFileSystemSpecificationTests
rameel Aug 10, 2025
51ee462
Update tests for SubFileSystem
rameel Aug 10, 2025
c322ecc
Clean up GCS test setup: remove commented-out emulator host configura…
rameel Aug 10, 2025
c17f118
Optimize directory emptiness check
rameel Aug 10, 2025
fa16f98
Remove debug output for expected exceptions
rameel Aug 10, 2025
c6d4385
GlobbingFileSystem: Match directory paths against glob patterns corre…
rameel Aug 10, 2025
d15201e
Add virtual glob pattern matching methods for file system operations
rameel Aug 10, 2025
139b67d
Optimize S3 glob pattern matching with batch listing and local filtering
rameel Aug 10, 2025
43c7cc0
Optimize Azure Blobs glob pattern matching with batch listing and loc…
rameel Aug 10, 2025
3703081
Optimize GCS glob pattern matching with batch listing and local filte…
rameel Aug 10, 2025
7cdb9f4
Throw UnauthorizedAccessException when deleting artificial read-only …
rameel Aug 10, 2025
e7335ca
GlobbingFileSystem: Update tests
rameel Aug 10, 2025
98f919e
Improve VirtualPath.GetDirectoryName XML documentation
rameel Aug 10, 2025
ef1b128
Clean up test resources
rameel Aug 10, 2025
fe12a1c
CompositeFileSystem: Update tests
rameel Aug 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions src/Ramstack.FileSystem.Abstractions/Null/NotFoundDirectory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Ramstack.FileSystem.Null;
/// <summary>
/// Represents a non-existing directory.
/// </summary>
public class NotFoundDirectory : VirtualDirectory
public sealed class NotFoundDirectory : VirtualDirectory
{
/// <inheritdoc />
public override IVirtualFileSystem FileSystem { get; }
Expand All @@ -27,8 +27,11 @@ protected override ValueTask<bool> ExistsCoreAsync(CancellationToken cancellatio
default;

/// <inheritdoc />
protected override ValueTask CreateCoreAsync(CancellationToken cancellationToken) =>
default;
protected override ValueTask CreateCoreAsync(CancellationToken cancellationToken)
{
Error_UnauthorizedAccess(FullName);
return default;
}

/// <inheritdoc />
protected override ValueTask DeleteCoreAsync(CancellationToken cancellationToken) =>
Expand All @@ -37,4 +40,8 @@ protected override ValueTask DeleteCoreAsync(CancellationToken cancellationToken
/// <inheritdoc />
protected override IAsyncEnumerable<VirtualNode> GetFileNodesCoreAsync(CancellationToken cancellationToken) =>
Array.Empty<VirtualNode>().ToAsyncEnumerable();

[DoesNotReturn]
private static void Error_UnauthorizedAccess(string path) =>
throw new UnauthorizedAccessException($"Access to the path '{path}' is denied.");
}
8 changes: 5 additions & 3 deletions src/Ramstack.FileSystem.Abstractions/Null/NotFoundFile.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using System.Diagnostics.CodeAnalysis;

namespace Ramstack.FileSystem.Null;

/// <summary>
Expand Down Expand Up @@ -43,7 +41,7 @@ protected override ValueTask<Stream> OpenWriteCoreAsync(CancellationToken cancel
/// <inheritdoc />
protected override ValueTask WriteCoreAsync(Stream stream, bool overwrite, CancellationToken cancellationToken)
{
Error_FileNotFound(FullName);
Error_UnauthorizedAccess(FullName);
return default;
}

Expand All @@ -54,4 +52,8 @@ protected override ValueTask DeleteCoreAsync(CancellationToken cancellationToken
[DoesNotReturn]
private static void Error_FileNotFound(string path) =>
throw new FileNotFoundException($"Unable to find file '{path}'.", path);

[DoesNotReturn]
private static void Error_UnauthorizedAccess(string path) =>
throw new UnauthorizedAccessException($"Access to the path '{path}' is denied.");
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

<ItemGroup>
<Using Include="System.Diagnostics" />
<Using Include="System.Diagnostics.CodeAnalysis" />
<Using Include="System.Runtime.CompilerServices" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub">
<PrivateAssets>all</PrivateAssets>
Expand Down
109 changes: 74 additions & 35 deletions src/Ramstack.FileSystem.Abstractions/VirtualDirectory.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;

using Ramstack.Globbing.Traversal;

namespace Ramstack.FileSystem;
Expand Down Expand Up @@ -121,16 +118,7 @@ public IAsyncEnumerable<VirtualDirectory> GetDirectoriesAsync(CancellationToken
public IAsyncEnumerable<VirtualNode> GetFileNodesAsync(string[] patterns, string[]? excludes, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(patterns);

return new FileTreeAsyncEnumerable<VirtualNode, VirtualNode>(this, cancellationToken)
{
Patterns = patterns,
Excludes = excludes ?? [],
FileNameSelector = node => node.FullName,
ShouldRecursePredicate = node => node is VirtualDirectory,
ChildrenSelector = (node, token) => ((VirtualDirectory)node).GetFileNodesCoreAsync(token),
ResultSelector = node => node
};
return GetFileNodesCoreAsync(patterns, excludes, cancellationToken);
}

/// <summary>
Expand All @@ -146,17 +134,7 @@ public IAsyncEnumerable<VirtualNode> GetFileNodesAsync(string[] patterns, string
public IAsyncEnumerable<VirtualFile> GetFilesAsync(string[] patterns, string[]? excludes, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(patterns);

return new FileTreeAsyncEnumerable<VirtualNode, VirtualFile>(this, cancellationToken)
{
Patterns = patterns,
Excludes = excludes ?? [],
FileNameSelector = node => node.FullName,
ShouldIncludePredicate = node => node is VirtualFile,
ShouldRecursePredicate = node => node is VirtualDirectory,
ChildrenSelector = (node, token) => ((VirtualDirectory)node).GetFileNodesCoreAsync(token),
ResultSelector = node => (VirtualFile)node
};
return GetFilesCoreAsync(patterns, excludes, cancellationToken);
}

/// <summary>
Expand All @@ -172,17 +150,7 @@ public IAsyncEnumerable<VirtualFile> GetFilesAsync(string[] patterns, string[]?
public IAsyncEnumerable<VirtualDirectory> GetDirectoriesAsync(string[] patterns, string[]? excludes, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(patterns);

return new FileTreeAsyncEnumerable<VirtualNode, VirtualDirectory>(this, cancellationToken)
{
Patterns = patterns,
Excludes = excludes ?? [],
FileNameSelector = node => node.FullName,
ShouldIncludePredicate = node => node is VirtualDirectory,
ShouldRecursePredicate = node => node is VirtualDirectory,
ChildrenSelector = (node, token) => ((VirtualDirectory)node).GetFileNodesCoreAsync(token),
ResultSelector = node => (VirtualDirectory)node
};
return GetDirectoriesCoreAsync(patterns, excludes, cancellationToken);
}

/// <summary>
Expand Down Expand Up @@ -243,4 +211,75 @@ protected virtual async IAsyncEnumerable<VirtualDirectory> GetDirectoriesCoreAsy
yield return directory;
}
}

/// <summary>
/// Core implementation for asynchronously returning an async-enumerable collection of file nodes (both directories and files)
/// within the current directory that match any of the specified glob patterns.
/// </summary>
/// <param name="patterns">An array of glob patterns to match against the names of file nodes.</param>
/// <param name="excludes">An optional array of glob patterns to exclude file nodes.</param>
/// <param name="cancellationToken">An optional cancellation token to cancel the operation.</param>
/// <returns>
/// An async-enumerable collection of <see cref="VirtualNode"/> instances.
/// </returns>
protected virtual IAsyncEnumerable<VirtualNode> GetFileNodesCoreAsync(string[] patterns, string[]? excludes, CancellationToken cancellationToken)
{
return new FileTreeAsyncEnumerable<VirtualNode, VirtualNode>(this, cancellationToken)
{
Patterns = patterns,
Excludes = excludes ?? [],
FileNameSelector = node => node.Name,
ShouldRecursePredicate = node => node is VirtualDirectory,
ChildrenSelector = (node, token) => ((VirtualDirectory)node).GetFileNodesCoreAsync(token),
ResultSelector = node => node
};
}

/// <summary>
/// Core implementation for asynchronously returning an async-enumerable collection of files within the current directory
/// that match any of the specified glob patterns.
/// </summary>
/// <param name="patterns">An array of glob patterns to match against the names of files.</param>
/// <param name="excludes">An optional array of glob patterns to exclude files.</param>
/// <param name="cancellationToken">An optional cancellation token to cancel the operation.</param>
/// <returns>
/// An async-enumerable collection of <see cref="VirtualFile"/> instances.
/// </returns>
protected virtual IAsyncEnumerable<VirtualFile> GetFilesCoreAsync(string[] patterns, string[]? excludes, CancellationToken cancellationToken)
{
return new FileTreeAsyncEnumerable<VirtualNode, VirtualFile>(this, cancellationToken)
{
Patterns = patterns,
Excludes = excludes ?? [],
FileNameSelector = node => node.Name,
ShouldIncludePredicate = node => node is VirtualFile,
ShouldRecursePredicate = node => node is VirtualDirectory,
ChildrenSelector = (node, token) => ((VirtualDirectory)node).GetFileNodesCoreAsync(token),
ResultSelector = node => (VirtualFile)node
};
}

/// <summary>
/// Core implementation for asynchronously returning an async-enumerable collection of directories within the current directory
/// that match any of the specified glob patterns.
/// </summary>
/// <param name="patterns">An array of glob patterns to match against the names of directories.</param>
/// <param name="excludes">An optional array of glob patterns to exclude directories.</param>
/// <param name="cancellationToken">An optional cancellation token to cancel the operation.</param>
/// <returns>
/// An async-enumerable collection of <see cref="VirtualDirectory"/> instances.
/// </returns>
protected virtual IAsyncEnumerable<VirtualDirectory> GetDirectoriesCoreAsync(string[] patterns, string[]? excludes, CancellationToken cancellationToken)
{
return new FileTreeAsyncEnumerable<VirtualNode, VirtualDirectory>(this, cancellationToken)
{
Patterns = patterns,
Excludes = excludes ?? [],
FileNameSelector = node => node.Name,
ShouldIncludePredicate = node => node is VirtualDirectory,
ShouldRecursePredicate = node => node is VirtualDirectory,
ChildrenSelector = (node, token) => ((VirtualDirectory)node).GetFileNodesCoreAsync(token),
ResultSelector = node => (VirtualDirectory)node
};
}
}
2 changes: 0 additions & 2 deletions src/Ramstack.FileSystem.Abstractions/VirtualFile.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using System.Diagnostics;

namespace Ramstack.FileSystem;

/// <summary>
Expand Down
3 changes: 0 additions & 3 deletions src/Ramstack.FileSystem.Abstractions/VirtualNode.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;

namespace Ramstack.FileSystem;

/// <summary>
Expand Down
2 changes: 0 additions & 2 deletions src/Ramstack.FileSystem.Abstractions/VirtualNodeProperties.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using System.Diagnostics;

namespace Ramstack.FileSystem;

/// <summary>
Expand Down
6 changes: 4 additions & 2 deletions src/Ramstack.FileSystem.Abstractions/VirtualPath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ public static ReadOnlySpan<char> GetFileName(ReadOnlySpan<char> path)
/// </summary>
/// <param name="path">The path to retrieve the directory portion from.</param>
/// <returns>
/// Directory portion for <paramref name="path"/>, or an empty string if the path denotes a root directory.
/// The directory portion of <paramref name="path"/>, or an empty span if <paramref name="path"/>
/// is empty or denotes a root directory.
/// </returns>
public static string GetDirectoryName(string path)
{
Expand All @@ -140,7 +141,8 @@ public static string GetDirectoryName(string path)
/// </summary>
/// <param name="path">The path to retrieve the directory portion from.</param>
/// <returns>
/// Directory portion for <paramref name="path"/>, or an empty string if path denotes a root directory.
/// The directory portion of <paramref name="path"/>, or an empty span if <paramref name="path"/>
/// is empty or denotes a root directory.
/// </returns>
public static ReadOnlySpan<char> GetDirectoryName(ReadOnlySpan<char> path)
{
Expand Down
Loading
Loading