diff --git a/.editorconfig b/.editorconfig
index 03ce37f..426c83c 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,71 +1,89 @@
-root = true
-
-[*]
-charset = utf-8
-indent_size = 4
-indent_style = space
-insert_final_newline = true
-trim_trailing_whitespace = true
-
-[*.{yml,props,targets,csproj}]
-indent_size = 2
-
-# IDE0059: Unnecessary assigment of a value
-dotnet_diagnostic.IDE0059.severity = none
-
-# IDE0060: Remove unused parameter
-dotnet_diagnostic.IDE0060.severity = none
-
-# IDE0079: Remove unnecessary suppression
-# dotnet_diagnostic.IDE0079.severity = none
-
-# IDE0079: Remove unnecessary suppression
-dotnet_remove_unnecessary_suppression_exclusions = category:ReSharper
-
-# CS8600: Converting null literal or possible null value to non-nullable type.
-dotnet_diagnostic.CS8600.severity = error
-
-# CS8601: Possible null reference assignment.
-dotnet_diagnostic.CS8601.severity = error
-
-# CS8602: Dereference of a possibly null reference.
-dotnet_diagnostic.CS8602.severity = error
-
-# CS8603: Possible null reference return.
-dotnet_diagnostic.CS8603.severity = error
-
-# CS8604: Possible null reference argument.
-dotnet_diagnostic.CS8604.severity = error
-
-# CS8610: Nullability of reference types in type of parameter doesn't match overridden member.
-dotnet_diagnostic.CS8610.severity = error
-
-# CS8613: Nullability of reference types in return type doesn't match implicitly implemented member.
-dotnet_diagnostic.CS8613.severity = error
-
-# CS8614: Nullability of reference types in type of parameter doesn't match implicitly implemented member.
-dotnet_diagnostic.CS8614.severity = error
-
-# CS8618: Non-nullable field is uninitialized. Consider declaring as nullable.
-dotnet_diagnostic.CS8618.severity = error
-
-# CS8619: Nullability of reference types in value doesn't match target type.
-dotnet_diagnostic.CS8619.severity = error
-
-# CS8620: Argument cannot be used for parameter due to differences in the nullability of reference types.
-dotnet_diagnostic.CS8620.severity = error
-
-# CS8625: Cannot convert null literal to non-nullable reference type.
-dotnet_diagnostic.CS8625.severity = error
-
-# CS8634: The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'class' constraint.
-dotnet_diagnostic.CS8634.severity = error
-
-# CS8643: Nullability of reference types in explicit interface specifier doesn't match interface implemented by the type.
-dotnet_diagnostic.CS8643.severity = error
-
-# CS8653: A default expression introduces a null value for a type parameter.
-dotnet_diagnostic.CS8653.severity = error
-
-# CS8714: The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'notnull' constraint.
-dotnet_diagnostic.CS8714.severity = error
+root = true
+
+[*]
+charset = utf-8
+indent_size = 4
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.{yml,yaml}]
+indent_size = 2
+
+[*.{slnx,csproj,proj,props,targets}]
+indent_size = 2
+
+[*.cs]
+resharper_csharp_max_line_length = 160
+
+# Using directive options
+dotnet_sort_system_directives_first = true
+dotnet_separate_import_directive_groups = true
+
+# Prefer var whenever possible
+csharp_style_var_for_built_in_types = true:suggestion
+csharp_style_var_when_type_is_apparent = true:suggestion
+csharp_style_var_elsewhere = true:suggestion
+
+# Indentation preferences
+csharp_indent_labels = no_change
+
+# IDE0059: Unnecessary assigment of a value
+dotnet_diagnostic.IDE0059.severity = none
+
+# IDE0060: Remove unused parameter
+dotnet_diagnostic.IDE0060.severity = none
+
+# IDE0079: Remove unnecessary suppression
+# dotnet_diagnostic.IDE0079.severity = none
+
+# IDE0079: Remove unnecessary suppression
+dotnet_remove_unnecessary_suppression_exclusions = category:ReSharper
+
+# CS8600: Converting null literal or possible null value to non-nullable type.
+dotnet_diagnostic.CS8600.severity = error
+
+# CS8601: Possible null reference assignment.
+dotnet_diagnostic.CS8601.severity = error
+
+# CS8602: Dereference of a possibly null reference.
+dotnet_diagnostic.CS8602.severity = error
+
+# CS8603: Possible null reference return.
+dotnet_diagnostic.CS8603.severity = error
+
+# CS8604: Possible null reference argument.
+dotnet_diagnostic.CS8604.severity = error
+
+# CS8610: Nullability of reference types in type of parameter doesn't match overridden member.
+dotnet_diagnostic.CS8610.severity = error
+
+# CS8613: Nullability of reference types in return type doesn't match implicitly implemented member.
+dotnet_diagnostic.CS8613.severity = error
+
+# CS8614: Nullability of reference types in type of parameter doesn't match implicitly implemented member.
+dotnet_diagnostic.CS8614.severity = error
+
+# CS8618: Non-nullable field is uninitialized. Consider declaring as nullable.
+dotnet_diagnostic.CS8618.severity = error
+
+# CS8619: Nullability of reference types in value doesn't match target type.
+dotnet_diagnostic.CS8619.severity = error
+
+# CS8620: Argument cannot be used for parameter due to differences in the nullability of reference types.
+dotnet_diagnostic.CS8620.severity = error
+
+# CS8625: Cannot convert null literal to non-nullable reference type.
+dotnet_diagnostic.CS8625.severity = error
+
+# CS8634: The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'class' constraint.
+dotnet_diagnostic.CS8634.severity = error
+
+# CS8643: Nullability of reference types in explicit interface specifier doesn't match interface implemented by the type.
+dotnet_diagnostic.CS8643.severity = error
+
+# CS8653: A default expression introduces a null value for a type parameter.
+dotnet_diagnostic.CS8653.severity = error
+
+# CS8714: The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'notnull' constraint.
+dotnet_diagnostic.CS8714.severity = error
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..176a458
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+* text=auto
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 17d779c..b4b2a07 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -1,38 +1,38 @@
-name: Publish
-
-on:
- push:
- tags:
- - "[0-9]+.[0-9]+.[0-9]+"
- - "[0-9]+.[0-9]+.[0-9]+-[a-z]+.[0-9]+"
-
-permissions:
- contents: write
-
-jobs:
- publish:
- name: "Publish packages"
- runs-on: ubuntu-latest
- steps:
- - name: Install .NET
- uses: actions/setup-dotnet@v4
- with:
- dotnet-version: 10.x
-
- - name: Checkout
- uses: actions/checkout@v4
-
- - name: Build Solution
- run: dotnet build -c Release
-
- - name: Create NuGet Packages
- run: dotnet pack -c Release -o ./nuget --no-build
-
- - name: Publish NuGet Packages
- run: dotnet nuget push ./nuget/*.nupkg --api-key ${{secrets.NUGET_API_KEY}} --skip-duplicate -s https://api.nuget.org/v3/index.json
-
- - name: Create GitHub Release
- uses: softprops/action-gh-release@v2
- with:
- draft: true
- files: nuget/*
+name: Publish
+
+on:
+ push:
+ tags:
+ - "[0-9]+.[0-9]+.[0-9]+"
+ - "[0-9]+.[0-9]+.[0-9]+-[a-z]+.[0-9]+"
+
+permissions:
+ contents: write
+
+jobs:
+ publish:
+ name: "Publish packages"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Install .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 10.x
+
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Build Solution
+ run: dotnet build -c Release
+
+ - name: Create NuGet Packages
+ run: dotnet pack -c Release -o ./nuget --no-build
+
+ - name: Publish NuGet Packages
+ run: dotnet nuget push ./nuget/*.nupkg --api-key ${{secrets.NUGET_API_KEY}} --skip-duplicate -s https://api.nuget.org/v3/index.json
+
+ - name: Create GitHub Release
+ uses: softprops/action-gh-release@v2
+ with:
+ draft: true
+ files: nuget/*
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 37da93f..f562c3a 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -1,95 +1,95 @@
-name: Build & Test
-
-on:
- pull_request:
- branches: [main]
-
-jobs:
-
- main:
- name: "Test: Main projects, ${{ matrix.os.name }}"
- runs-on: ${{ matrix.os.id }}
- strategy:
- matrix:
- os:
- - id: windows-latest
- name: Windows
- - id: ubuntu-latest
- name: Ubuntu
- steps:
- - name: Setup .NET
- uses: actions/setup-dotnet@v4
- with:
- dotnet-version: |
- 6.x
- 7.x
- 8.x
- 9.x
- 10.x
- - name: Checkout
- uses: actions/checkout@v4
- - name: Build (Debug)
- run: dotnet build -c Debug
- - name: Build (Release)
- run: dotnet build -c Release
- - name: Test (Debug)
- run: dotnet test -c Debug --no-build --filter "TestCategory!~Cloud"
- - name: Test (Release)
- run: dotnet test -c Release --no-build --filter "TestCategory!~Cloud"
-
- azure:
- name: "Test: AzureFileSystem"
- runs-on: ubuntu-latest
- steps:
- - name: Start Azurite
- run: |
- docker run -d \
- -p 10000:10000 \
- -p 10001:10001 \
- -p 10002:10002 \
- mcr.microsoft.com/azure-storage/azurite \
- azurite --skipApiVersionCheck \
- --blobHost 0.0.0.0 \
- --queueHost 0.0.0.0 \
- --tableHost 0.0.0.0
- - name: Setup .NET
- uses: actions/setup-dotnet@v4
- with:
- dotnet-version: |
- 6.x
- 7.x
- 8.x
- 9.x
- 10.x
- - name: Checkout
- uses: actions/checkout@v4
- - name: Build
- run: dotnet build -c Debug
- - name: Test
- run: dotnet test --no-build --filter TestCategory=Cloud:Azure -maxcpucount:1
-
- s3:
- name: "Test: S3FileSystem"
- runs-on: ubuntu-latest
- services:
- rustfs:
- image: docker.io/rustfs/rustfs:latest
- ports:
- - 9000:9000
- - 9001:9001
- steps:
- - name: Setup .NET
- uses: actions/setup-dotnet@v4
- with:
- dotnet-version: |
- 6.x
- 7.x
- 8.x
- 9.x
- 10.x
- - name: Checkout
- uses: actions/checkout@v4
- - name: Build
- run: dotnet build -c Debug
- - name: Test
- run: dotnet test --no-build --filter TestCategory=Cloud:Amazon -maxcpucount:1
+name: Build & Test
+
+on:
+ pull_request:
+ branches: [main]
+
+jobs:
+
+ main:
+ name: "Test: Main projects, ${{ matrix.os.name }}"
+ runs-on: ${{ matrix.os.id }}
+ strategy:
+ matrix:
+ os:
+ - id: windows-latest
+ name: Windows
+ - id: ubuntu-latest
+ name: Ubuntu
+ steps:
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: |
+ 6.x
+ 7.x
+ 8.x
+ 9.x
+ 10.x
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Build (Debug)
+ run: dotnet build -c Debug
+ - name: Build (Release)
+ run: dotnet build -c Release
+ - name: Test (Debug)
+ run: dotnet test -c Debug --no-build --filter "TestCategory!~Cloud"
+ - name: Test (Release)
+ run: dotnet test -c Release --no-build --filter "TestCategory!~Cloud"
+
+ azure:
+ name: "Test: AzureFileSystem"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Start Azurite
+ run: |
+ docker run -d \
+ -p 10000:10000 \
+ -p 10001:10001 \
+ -p 10002:10002 \
+ mcr.microsoft.com/azure-storage/azurite \
+ azurite --skipApiVersionCheck \
+ --blobHost 0.0.0.0 \
+ --queueHost 0.0.0.0 \
+ --tableHost 0.0.0.0
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: |
+ 6.x
+ 7.x
+ 8.x
+ 9.x
+ 10.x
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Build
+ run: dotnet build -c Debug
+ - name: Test
+ run: dotnet test --no-build --filter TestCategory=Cloud:Azure -maxcpucount:1
+
+ s3:
+ name: "Test: S3FileSystem"
+ runs-on: ubuntu-latest
+ services:
+ rustfs:
+ image: docker.io/rustfs/rustfs:latest
+ ports:
+ - 9000:9000
+ - 9001:9001
+ steps:
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: |
+ 6.x
+ 7.x
+ 8.x
+ 9.x
+ 10.x
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Build
+ run: dotnet build -c Debug
+ - name: Test
+ run: dotnet test --no-build --filter TestCategory=Cloud:Amazon -maxcpucount:1
diff --git a/.gitignore b/.gitignore
index 4f81b1b..bd6adb1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,37 +1,37 @@
-#Ignore thumbnails created by windows
-[Tt]humbs.db
-
-#Ignore files build by Visual Studio
-*.obj
-*.exe
-*.pdb
-*.user
-*.patch
-*.aps
-*.pch
-*.vspscc
-*_i.c
-*_p.c
-*.ncb
-*.suo
-*.tlb
-*.tlh
-*.bak
-*.cache
-*.ilk
-*.log
-*.lib
-*.sbr
-*.cs.ide
-[Dd]ebug*/
-[Rr]elease*/
-bin/
-obj/
-.vs/
-.vscode/
-*.ReSharper
-*.ReSharper.user
-[Tt]est[Rr]esult*
-
-#Ignore Rider/Idea files
-.idea/
+#Ignore thumbnails created by windows
+[Tt]humbs.db
+
+#Ignore files build by Visual Studio
+*.obj
+*.exe
+*.pdb
+*.user
+*.patch
+*.aps
+*.pch
+*.vspscc
+*_i.c
+*_p.c
+*.ncb
+*.suo
+*.tlb
+*.tlh
+*.bak
+*.cache
+*.ilk
+*.log
+*.lib
+*.sbr
+*.cs.ide
+[Dd]ebug*/
+[Rr]elease*/
+bin/
+obj/
+.vs/
+.vscode/
+*.ReSharper
+*.ReSharper.user
+[Tt]est[Rr]esult*
+
+#Ignore Rider/Idea files
+.idea/
diff --git a/Directory.Build.props b/Directory.Build.props
index bf61882..ed14ea7 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,5 +1,5 @@
-
-
- true
-
-
+
+
+ true
+
+
diff --git a/Directory.Packages.props b/Directory.Packages.props
index aec1e52..cda6b3d 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -1,21 +1,21 @@
-
-
-
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/LICENSE b/LICENSE
index 6666765..92e3431 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,9 +1,9 @@
-MIT License
-
-Copyright 2024 Rameel (https://github.com/rameel)
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+MIT License
+
+Copyright 2024 Rameel (https://github.com/rameel)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
index cedb310..9e01180 100644
--- a/README.md
+++ b/README.md
@@ -1,166 +1,166 @@
-# Ramstack.FileSystem
-[](https://nuget.org/packages/Ramstack.FileSystem.Abstractions)
-[](https://github.com/rameel/ramstack.filesystem/blob/main/LICENSE)
-
-A .NET library providing a virtual file system abstraction.
-
-**For current implementations, see [Related Projects](#related-projects)**
-
-## Overview
-
-The primary interface is `IVirtualFileSystem`, which exposes methods to:
-- Access files (`VirtualFile`).
-- Access directories (`VirtualDirectory`).
-
-### VirtualFile
-
-The `VirtualFile` class provides properties and methods for creating, deleting, copying, and opening files within the virtual file system.
-
-```csharp
-using Ramstack.FileSystem;
-
-public class Program
-{
- public static async Task Sample(IVirtualFileSystem fs)
- {
- var file = fs.GetFile("/sample/hello.txt");
-
- Console.WriteLine($"Writing 'Hello, World!' to '{file.FullName}'");
-
- await using (Stream stream = await file.OpenWriteAsync())
- await using (StreamWriter writer = new StreamWriter(stream))
- {
- await writer.WriteLineAsync("Hello, World!");
- }
-
- Console.WriteLine($"Reading contents of '{file.FullName}'");
- using (StreamReader reader = await file.OpenTextAsync())
- {
- Console.WriteLine(await reader.ReadToEndAsync());
- }
-
- // Replace content of the file with data from an existing file
- await using FileStream input = File.OpenRead(@"D:\sample\hello-world.txt");
- await file.WriteAsync(input, overwrite: true);
-
- Console.WriteLine($"The file '{file.Name}' has a length of {await file.GetLengthAsync()} bytes");
-
- Console.WriteLine($"Deleting file '{file.FullName}'");
- await file.DeleteAsync();
- }
-}
-```
-
-The `VirtualFile` class provides the following properties to retrieve information about a file:
-- `FileSystem`: Gets the `IVirtualFileSystem` associated with the file.
-- `Directory`: Gets the `VirtualDirectory` representing the parent directory.
-- `DirectoryName`: Gets the full path of the parent directory.
-- `IsReadOnly`: Indicates whether the file is read-only.
-- `Name`: Gets the name of the file.
-- `Extension`: Gets the extension of the file.
-
-The `VirtualFile` class also exposes a `GetPropertiesAsync` method to retrieve file properties:
-- `Length`: Gets the size of the file in bytes.
-- `Exists`: Indicates whether the file exists.
-- `CreationTime`: Gets the creation time of the file.
-- `LastAccessTime`: Gets the last access time of the file.
-- `LastWriteTime`: Gets the last modification time of the file.
-
-For convenience, many methods specific to `VirtualFile` are also available as extension methods on `IVirtualFileSystem`.
-
-For example, if we know the path of the file we want to read, we don't need to obtain a `VirtualFile` object explicitly:
-```csharp
-using StreamReader reader = await fs.OpenTextAsync("/docs/README", Encoding.UTF8);
-Console.WriteLine(await reader.ReadToEndAsync());
-```
-
-### VirtualDirectory
-
-The `VirtualDirectory` class provides properties and methods for creating, deleting, and enumerating directories and subdirectories.
-
-```csharp
-public static async Task PrintFilesAsync(VirtualDirectory directory, string padding = "", CancellationToken cancellationToken = default)
-{
- await foreach (VirtualNode node in directory.GetFileNodesAsync(cancellationToken))
- {
- Console.WriteLine($"{padding}{node.Name}");
-
- if (node is VirtualDirectory dir)
- {
- await PrintFilesAsync(dir, padding + " ", cancellationToken);
- }
- }
-}
-
-// Print the file tree
-await PrintFilesAsync(fs.GetDirectory("/sample"));
-```
-
-The `VirtualDirectory` class provides the following properties to retrieve information about a directory:
-- `FileSystem`: Gets the `IVirtualFileSystem` associated with the directory.
-- `IsRoot`: Indicates whether the directory is the root directory.
-- `Parent`: Gets the `VirtualDirectory` representing the parent directory.
-- `DirectoryName`: Gets the full path of the parent directory.
-- `IsReadOnly`: Indicates whether the directory is read-only.
-- `Name`: Gets the name of the directory.
-
-The `VirtualDirectory` class also exposes a `GetPropertiesAsync` method to retrieve directory properties, if available:
-- `Exists`: Indicates whether the directory exists.
-- `CreationTime`: Gets the creation time of the directory.
-- `LastAccessTime`: Gets the last access time of the directory.
-- `LastWriteTime`: Gets the last modification time of the directory.
-- `Length`: Always returns `0` for directories.
-
-For convenience, many methods specific to `VirtualDirectory` are also available as extension methods on `IVirtualFileSystem`.
-
-For example, if we know the path of the desired directory, we don't need to obtain a `VirtualDirectory` object explicitly:
-```csharp
-await foreach (VirtualFile file in fs.GetFilesAsync("/sample/directory"))
-{
- Console.WriteLine($"Processing: {file.FullName}");
-}
-
-// Delete the directory recursively
-await fs.DeleteDirectoryAsync("/sample/directory");
-```
-
-## Remarks
-
-The file system in use may be read-only, and as a result, any modifying operations on files and directories will throw an exception.
-To check if the file system is read-only, the `IVirtualFileSystem`, `VirtualFile`, and `VirtualDirectory` classes provide the `IsReadOnly` property.
-
-```csharp
-if (!fs.IsReadOnly)
-{
- await fs.DeleteFileAsync("/hello.txt");
-}
-```
-
-## Related Projects
-- [Ramstack.FileSystem.Abstractions](https://www.nuget.org/packages/Ramstack.FileSystem.Abstractions) - Provides a virtual file system abstraction.
-- [Ramstack.FileSystem.Physical](https://www.nuget.org/packages/Ramstack.FileSystem.Physical) - Provides an implementation based on the local file system.
-- [Ramstack.FileSystem.Azure](https://www.nuget.org/packages/Ramstack.FileSystem.Azure) - Provides an implementation using Azure Blob Storage.
-- [Ramstack.FileSystem.Amazon](https://www.nuget.org/packages/Ramstack.FileSystem.Amazon) - Provides an implementation using Amazon S3 storage.
-- [Ramstack.FileSystem.Google](https://www.nuget.org/packages/Ramstack.FileSystem.Google) - Provides an implementation using Google Cloud Storage.
-- [Ramstack.FileSystem.Readonly](https://www.nuget.org/packages/Ramstack.FileSystem.Readonly) - Provides a read-only wrapper for the underlying file system.
-- [Ramstack.FileSystem.Globbing](https://www.nuget.org/packages/Ramstack.FileSystem.Globbing) - Wraps the file system, filtering files and directories using glob patterns.
-- [Ramstack.FileSystem.Prefixed](https://www.nuget.org/packages/Ramstack.FileSystem.Prefixed) - Adds a prefix to file paths within the underlying file system.
-- [Ramstack.FileSystem.Sub](https://www.nuget.org/packages/Ramstack.FileSystem.Sub) - Wraps the underlying file system, restricting access to a specific subpath.
-- [Ramstack.FileSystem.Adapters](https://www.nuget.org/packages/Ramstack.FileSystem.Adapters) - Provides integration with `Microsoft.Extensions.FileProviders`.
-- [Ramstack.FileSystem.Composite](https://www.nuget.org/packages/Ramstack.FileSystem.Composite) - Provides an implementation that combines multiple file systems into a single composite file system.
-
-## Supported Versions
-
-| | Version |
-|------|----------------|
-| .NET | 6, 7, 8, 9, 10 |
-
-## Contributions
-
-Bug reports and contributions are welcome.
-
-## License
-
-This package is released as open source under the **MIT License**.
-See the [LICENSE](https://github.com/rameel/ramstack.filesystem/blob/main/LICENSE) file for more details.
-
+# Ramstack.FileSystem
+[](https://nuget.org/packages/Ramstack.FileSystem.Abstractions)
+[](https://github.com/rameel/ramstack.filesystem/blob/main/LICENSE)
+
+A .NET library providing a virtual file system abstraction.
+
+**For current implementations, see [Related Projects](#related-projects)**
+
+## Overview
+
+The primary interface is `IVirtualFileSystem`, which exposes methods to:
+- Access files (`VirtualFile`).
+- Access directories (`VirtualDirectory`).
+
+### VirtualFile
+
+The `VirtualFile` class provides properties and methods for creating, deleting, copying, and opening files within the virtual file system.
+
+```csharp
+using Ramstack.FileSystem;
+
+public class Program
+{
+ public static async Task Sample(IVirtualFileSystem fs)
+ {
+ var file = fs.GetFile("/sample/hello.txt");
+
+ Console.WriteLine($"Writing 'Hello, World!' to '{file.FullName}'");
+
+ await using (Stream stream = await file.OpenWriteAsync())
+ await using (StreamWriter writer = new StreamWriter(stream))
+ {
+ await writer.WriteLineAsync("Hello, World!");
+ }
+
+ Console.WriteLine($"Reading contents of '{file.FullName}'");
+ using (StreamReader reader = await file.OpenTextAsync())
+ {
+ Console.WriteLine(await reader.ReadToEndAsync());
+ }
+
+ // Replace content of the file with data from an existing file
+ await using FileStream input = File.OpenRead(@"D:\sample\hello-world.txt");
+ await file.WriteAsync(input, overwrite: true);
+
+ Console.WriteLine($"The file '{file.Name}' has a length of {await file.GetLengthAsync()} bytes");
+
+ Console.WriteLine($"Deleting file '{file.FullName}'");
+ await file.DeleteAsync();
+ }
+}
+```
+
+The `VirtualFile` class provides the following properties to retrieve information about a file:
+- `FileSystem`: Gets the `IVirtualFileSystem` associated with the file.
+- `Directory`: Gets the `VirtualDirectory` representing the parent directory.
+- `DirectoryName`: Gets the full path of the parent directory.
+- `IsReadOnly`: Indicates whether the file is read-only.
+- `Name`: Gets the name of the file.
+- `Extension`: Gets the extension of the file.
+
+The `VirtualFile` class also exposes a `GetPropertiesAsync` method to retrieve file properties:
+- `Length`: Gets the size of the file in bytes.
+- `Exists`: Indicates whether the file exists.
+- `CreationTime`: Gets the creation time of the file.
+- `LastAccessTime`: Gets the last access time of the file.
+- `LastWriteTime`: Gets the last modification time of the file.
+
+For convenience, many methods specific to `VirtualFile` are also available as extension methods on `IVirtualFileSystem`.
+
+For example, if we know the path of the file we want to read, we don't need to obtain a `VirtualFile` object explicitly:
+```csharp
+using StreamReader reader = await fs.OpenTextAsync("/docs/README", Encoding.UTF8);
+Console.WriteLine(await reader.ReadToEndAsync());
+```
+
+### VirtualDirectory
+
+The `VirtualDirectory` class provides properties and methods for creating, deleting, and enumerating directories and subdirectories.
+
+```csharp
+public static async Task PrintFilesAsync(VirtualDirectory directory, string padding = "", CancellationToken cancellationToken = default)
+{
+ await foreach (VirtualNode node in directory.GetFileNodesAsync(cancellationToken))
+ {
+ Console.WriteLine($"{padding}{node.Name}");
+
+ if (node is VirtualDirectory dir)
+ {
+ await PrintFilesAsync(dir, padding + " ", cancellationToken);
+ }
+ }
+}
+
+// Print the file tree
+await PrintFilesAsync(fs.GetDirectory("/sample"));
+```
+
+The `VirtualDirectory` class provides the following properties to retrieve information about a directory:
+- `FileSystem`: Gets the `IVirtualFileSystem` associated with the directory.
+- `IsRoot`: Indicates whether the directory is the root directory.
+- `Parent`: Gets the `VirtualDirectory` representing the parent directory.
+- `DirectoryName`: Gets the full path of the parent directory.
+- `IsReadOnly`: Indicates whether the directory is read-only.
+- `Name`: Gets the name of the directory.
+
+The `VirtualDirectory` class also exposes a `GetPropertiesAsync` method to retrieve directory properties, if available:
+- `Exists`: Indicates whether the directory exists.
+- `CreationTime`: Gets the creation time of the directory.
+- `LastAccessTime`: Gets the last access time of the directory.
+- `LastWriteTime`: Gets the last modification time of the directory.
+- `Length`: Always returns `0` for directories.
+
+For convenience, many methods specific to `VirtualDirectory` are also available as extension methods on `IVirtualFileSystem`.
+
+For example, if we know the path of the desired directory, we don't need to obtain a `VirtualDirectory` object explicitly:
+```csharp
+await foreach (VirtualFile file in fs.GetFilesAsync("/sample/directory"))
+{
+ Console.WriteLine($"Processing: {file.FullName}");
+}
+
+// Delete the directory recursively
+await fs.DeleteDirectoryAsync("/sample/directory");
+```
+
+## Remarks
+
+The file system in use may be read-only, and as a result, any modifying operations on files and directories will throw an exception.
+To check if the file system is read-only, the `IVirtualFileSystem`, `VirtualFile`, and `VirtualDirectory` classes provide the `IsReadOnly` property.
+
+```csharp
+if (!fs.IsReadOnly)
+{
+ await fs.DeleteFileAsync("/hello.txt");
+}
+```
+
+## Related Projects
+- [Ramstack.FileSystem.Abstractions](https://www.nuget.org/packages/Ramstack.FileSystem.Abstractions) - Provides a virtual file system abstraction.
+- [Ramstack.FileSystem.Physical](https://www.nuget.org/packages/Ramstack.FileSystem.Physical) - Provides an implementation based on the local file system.
+- [Ramstack.FileSystem.Azure](https://www.nuget.org/packages/Ramstack.FileSystem.Azure) - Provides an implementation using Azure Blob Storage.
+- [Ramstack.FileSystem.Amazon](https://www.nuget.org/packages/Ramstack.FileSystem.Amazon) - Provides an implementation using Amazon S3 storage.
+- [Ramstack.FileSystem.Google](https://www.nuget.org/packages/Ramstack.FileSystem.Google) - Provides an implementation using Google Cloud Storage.
+- [Ramstack.FileSystem.Readonly](https://www.nuget.org/packages/Ramstack.FileSystem.Readonly) - Provides a read-only wrapper for the underlying file system.
+- [Ramstack.FileSystem.Globbing](https://www.nuget.org/packages/Ramstack.FileSystem.Globbing) - Wraps the file system, filtering files and directories using glob patterns.
+- [Ramstack.FileSystem.Prefixed](https://www.nuget.org/packages/Ramstack.FileSystem.Prefixed) - Adds a prefix to file paths within the underlying file system.
+- [Ramstack.FileSystem.Sub](https://www.nuget.org/packages/Ramstack.FileSystem.Sub) - Wraps the underlying file system, restricting access to a specific subpath.
+- [Ramstack.FileSystem.Adapters](https://www.nuget.org/packages/Ramstack.FileSystem.Adapters) - Provides integration with `Microsoft.Extensions.FileProviders`.
+- [Ramstack.FileSystem.Composite](https://www.nuget.org/packages/Ramstack.FileSystem.Composite) - Provides an implementation that combines multiple file systems into a single composite file system.
+
+## Supported Versions
+
+| | Version |
+|------|----------------|
+| .NET | 6, 7, 8, 9, 10 |
+
+## Contributions
+
+Bug reports and contributions are welcome.
+
+## License
+
+This package is released as open source under the **MIT License**.
+See the [LICENSE](https://github.com/rameel/ramstack.filesystem/blob/main/LICENSE) file for more details.
+
diff --git a/Ramstack.FileSystem.slnx b/Ramstack.FileSystem.slnx
index e14578d..b9f2965 100644
--- a/Ramstack.FileSystem.slnx
+++ b/Ramstack.FileSystem.slnx
@@ -1,6 +1,7 @@
+
diff --git a/src/.editorconfig b/src/.editorconfig
index 9b27adf..c83167b 100644
--- a/src/.editorconfig
+++ b/src/.editorconfig
@@ -1,2 +1,2 @@
-[*.cs]
-configure_await_analysis_mode = library
+[*.cs]
+configure_await_analysis_mode = library
diff --git a/src/Ramstack.FileSystem.Abstractions/IVirtualFileSystem.cs b/src/Ramstack.FileSystem.Abstractions/IVirtualFileSystem.cs
index c2d071b..400066d 100644
--- a/src/Ramstack.FileSystem.Abstractions/IVirtualFileSystem.cs
+++ b/src/Ramstack.FileSystem.Abstractions/IVirtualFileSystem.cs
@@ -1,30 +1,30 @@
-namespace Ramstack.FileSystem;
-
-///
-/// Provides an abstraction for accessing files and directories within a virtual file system.
-///
-public interface IVirtualFileSystem : IDisposable
-{
- ///
- /// Gets a value indicating whether the file system is read-only.
- ///
- bool IsReadOnly { get; }
-
- ///
- /// Retrieves a file located at the specified path.
- ///
- /// The path of the file to locate.
- ///
- /// A instance representing the specified file.
- ///
- VirtualFile GetFile(string path);
-
- ///
- /// Retrieves a directory located at the specified path.
- ///
- /// The path of the directory to locate.
- ///
- /// A instance representing the specified directory.
- ///
- VirtualDirectory GetDirectory(string path);
-}
+namespace Ramstack.FileSystem;
+
+///
+/// Provides an abstraction for accessing files and directories within a virtual file system.
+///
+public interface IVirtualFileSystem : IDisposable
+{
+ ///
+ /// Gets a value indicating whether the file system is read-only.
+ ///
+ bool IsReadOnly { get; }
+
+ ///
+ /// Retrieves a file located at the specified path.
+ ///
+ /// The path of the file to locate.
+ ///
+ /// A instance representing the specified file.
+ ///
+ VirtualFile GetFile(string path);
+
+ ///
+ /// Retrieves a directory located at the specified path.
+ ///
+ /// The path of the directory to locate.
+ ///
+ /// A instance representing the specified directory.
+ ///
+ VirtualDirectory GetDirectory(string path);
+}
diff --git a/src/Ramstack.FileSystem.Abstractions/Null/NotFoundDirectory.cs b/src/Ramstack.FileSystem.Abstractions/Null/NotFoundDirectory.cs
index 5d05bab..cb6c9a4 100644
--- a/src/Ramstack.FileSystem.Abstractions/Null/NotFoundDirectory.cs
+++ b/src/Ramstack.FileSystem.Abstractions/Null/NotFoundDirectory.cs
@@ -1,47 +1,47 @@
-using Ramstack.FileSystem.Utilities;
-
-namespace Ramstack.FileSystem.Null;
-
-///
-/// Represents a non-existing directory.
-///
-public sealed class NotFoundDirectory : VirtualDirectory
-{
- ///
- public override IVirtualFileSystem FileSystem { get; }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The file system associated with this directory.
- /// The path of the directory.
- public NotFoundDirectory(IVirtualFileSystem fileSystem, string path) : base(VirtualPath.Normalize(path)) =>
- FileSystem = fileSystem;
-
- ///
- protected override ValueTask GetPropertiesCoreAsync(CancellationToken cancellationToken) =>
- default;
-
- ///
- protected override ValueTask ExistsCoreAsync(CancellationToken cancellationToken) =>
- default;
-
- ///
- protected override ValueTask CreateCoreAsync(CancellationToken cancellationToken)
- {
- Error_UnauthorizedAccess(FullName);
- return default;
- }
-
- ///
- protected override ValueTask DeleteCoreAsync(CancellationToken cancellationToken) =>
- default;
-
- ///
- protected override IAsyncEnumerable GetFileNodesCoreAsync(CancellationToken cancellationToken) =>
- Array.Empty().ToAsyncEnumerable();
-
- [DoesNotReturn]
- private static void Error_UnauthorizedAccess(string path) =>
- throw new UnauthorizedAccessException($"Access to the path '{path}' is denied.");
-}
+using Ramstack.FileSystem.Utilities;
+
+namespace Ramstack.FileSystem.Null;
+
+///
+/// Represents a non-existing directory.
+///
+public sealed class NotFoundDirectory : VirtualDirectory
+{
+ ///
+ public override IVirtualFileSystem FileSystem { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The file system associated with this directory.
+ /// The path of the directory.
+ public NotFoundDirectory(IVirtualFileSystem fileSystem, string path) : base(VirtualPath.Normalize(path)) =>
+ FileSystem = fileSystem;
+
+ ///
+ protected override ValueTask GetPropertiesCoreAsync(CancellationToken cancellationToken) =>
+ default;
+
+ ///
+ protected override ValueTask ExistsCoreAsync(CancellationToken cancellationToken) =>
+ default;
+
+ ///
+ protected override ValueTask CreateCoreAsync(CancellationToken cancellationToken)
+ {
+ Error_UnauthorizedAccess(FullName);
+ return default;
+ }
+
+ ///
+ protected override ValueTask DeleteCoreAsync(CancellationToken cancellationToken) =>
+ default;
+
+ ///
+ protected override IAsyncEnumerable GetFileNodesCoreAsync(CancellationToken cancellationToken) =>
+ Array.Empty().ToAsyncEnumerable();
+
+ [DoesNotReturn]
+ private static void Error_UnauthorizedAccess(string path) =>
+ throw new UnauthorizedAccessException($"Access to the path '{path}' is denied.");
+}
diff --git a/src/Ramstack.FileSystem.Abstractions/Null/NotFoundFile.cs b/src/Ramstack.FileSystem.Abstractions/Null/NotFoundFile.cs
index 241a8f4..c7cff63 100644
--- a/src/Ramstack.FileSystem.Abstractions/Null/NotFoundFile.cs
+++ b/src/Ramstack.FileSystem.Abstractions/Null/NotFoundFile.cs
@@ -1,59 +1,59 @@
-namespace Ramstack.FileSystem.Null;
-
-///
-/// Represents a non-existing file.
-///
-public sealed class NotFoundFile : VirtualFile
-{
- ///
- public override IVirtualFileSystem FileSystem { get; }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The file system associated with this file.
- /// The path of the file.
- public NotFoundFile(IVirtualFileSystem fileSystem, string path) : base(VirtualPath.Normalize(path)) =>
- FileSystem = fileSystem;
-
- ///
- protected override ValueTask GetPropertiesCoreAsync(CancellationToken cancellationToken) =>
- default;
-
- ///
- protected override ValueTask ExistsCoreAsync(CancellationToken cancellationToken) =>
- default;
-
- ///
- protected override ValueTask OpenReadCoreAsync(CancellationToken cancellationToken)
- {
- Error_FileNotFound(FullName);
- return default;
- }
-
- ///
- protected override ValueTask OpenWriteCoreAsync(CancellationToken cancellationToken)
- {
- Error_FileNotFound(FullName);
- return default;
- }
-
- ///
- protected override ValueTask WriteCoreAsync(Stream stream, bool overwrite, CancellationToken cancellationToken)
- {
- Error_UnauthorizedAccess(FullName);
- return default;
- }
-
- ///
- protected override ValueTask DeleteCoreAsync(CancellationToken cancellationToken) =>
- default;
-
- [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.");
-}
+namespace Ramstack.FileSystem.Null;
+
+///
+/// Represents a non-existing file.
+///
+public sealed class NotFoundFile : VirtualFile
+{
+ ///
+ public override IVirtualFileSystem FileSystem { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The file system associated with this file.
+ /// The path of the file.
+ public NotFoundFile(IVirtualFileSystem fileSystem, string path) : base(VirtualPath.Normalize(path)) =>
+ FileSystem = fileSystem;
+
+ ///
+ protected override ValueTask GetPropertiesCoreAsync(CancellationToken cancellationToken) =>
+ default;
+
+ ///
+ protected override ValueTask ExistsCoreAsync(CancellationToken cancellationToken) =>
+ default;
+
+ ///
+ protected override ValueTask OpenReadCoreAsync(CancellationToken cancellationToken)
+ {
+ Error_FileNotFound(FullName);
+ return default;
+ }
+
+ ///
+ protected override ValueTask OpenWriteCoreAsync(CancellationToken cancellationToken)
+ {
+ Error_FileNotFound(FullName);
+ return default;
+ }
+
+ ///
+ protected override ValueTask WriteCoreAsync(Stream stream, bool overwrite, CancellationToken cancellationToken)
+ {
+ Error_UnauthorizedAccess(FullName);
+ return default;
+ }
+
+ ///
+ protected override ValueTask DeleteCoreAsync(CancellationToken cancellationToken) =>
+ default;
+
+ [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.");
+}
diff --git a/src/Ramstack.FileSystem.Abstractions/Null/NullFileSystem.cs b/src/Ramstack.FileSystem.Abstractions/Null/NullFileSystem.cs
index f3a2d00..fa46224 100644
--- a/src/Ramstack.FileSystem.Abstractions/Null/NullFileSystem.cs
+++ b/src/Ramstack.FileSystem.Abstractions/Null/NullFileSystem.cs
@@ -1,24 +1,24 @@
-namespace Ramstack.FileSystem.Null;
-
-///
-/// Represents a virtual file system that does not provide any actual file or directory functionality.
-/// All operations on this file system result in a or .
-///
-public sealed class NullFileSystem : IVirtualFileSystem
-{
- ///
- public bool IsReadOnly => true;
-
- ///
- public VirtualFile GetFile(string path) =>
- new NotFoundFile(this, path);
-
- ///
- public VirtualDirectory GetDirectory(string path) =>
- new NotFoundDirectory(this, path);
-
- ///
- void IDisposable.Dispose()
- {
- }
-}
+namespace Ramstack.FileSystem.Null;
+
+///
+/// Represents a virtual file system that does not provide any actual file or directory functionality.
+/// All operations on this file system result in a or .
+///
+public sealed class NullFileSystem : IVirtualFileSystem
+{
+ ///
+ public bool IsReadOnly => true;
+
+ ///
+ public VirtualFile GetFile(string path) =>
+ new NotFoundFile(this, path);
+
+ ///
+ public VirtualDirectory GetDirectory(string path) =>
+ new NotFoundDirectory(this, path);
+
+ ///
+ void IDisposable.Dispose()
+ {
+ }
+}
diff --git a/src/Ramstack.FileSystem.Abstractions/Properties/AssemblyInfo.cs b/src/Ramstack.FileSystem.Abstractions/Properties/AssemblyInfo.cs
index b5274aa..c65b5dc 100644
--- a/src/Ramstack.FileSystem.Abstractions/Properties/AssemblyInfo.cs
+++ b/src/Ramstack.FileSystem.Abstractions/Properties/AssemblyInfo.cs
@@ -1 +1 @@
-[module: System.Runtime.CompilerServices.SkipLocalsInit]
+[module: System.Runtime.CompilerServices.SkipLocalsInit]
diff --git a/src/Ramstack.FileSystem.Abstractions/README.md b/src/Ramstack.FileSystem.Abstractions/README.md
index d4de1a7..a3edd65 100644
--- a/src/Ramstack.FileSystem.Abstractions/README.md
+++ b/src/Ramstack.FileSystem.Abstractions/README.md
@@ -1,172 +1,172 @@
-# Ramstack.FileSystem.Abstractions
-[](https://nuget.org/packages/Ramstack.FileSystem.Abstractions)
-[](https://github.com/rameel/ramstack.filesystem/blob/main/LICENSE)
-
-Provides a virtual file system abstraction.
-
-**For current implementations, see [Related Projects](#related-projects)**
-
-## Getting Started
-
-To install the `Ramstack.FileSystem.Abstractions` [NuGet package](https://www.nuget.org/packages/Ramstack.FileSystem.Abstractions)
-in your project, run the following command:
-
-```bash
-dotnet add package Ramstack.FileSystem.Abstractions
-```
-
-## Overview
-
-The primary interface is `IVirtualFileSystem`, which exposes methods to:
-- Access files (`VirtualFile`).
-- Access directories (`VirtualDirectory`).
-
-### VirtualFile
-
-The `VirtualFile` class provides properties and methods for creating, deleting, copying, and opening files within the virtual file system.
-
-```csharp
-using Ramstack.FileSystem;
-
-public class Program
-{
- public static async Task Sample(IVirtualFileSystem fs)
- {
- var file = fs.GetFile("/sample/hello.txt");
-
- Console.WriteLine($"Writing 'Hello, World!' to '{file.FullName}'");
-
- await using (Stream stream = await file.OpenWriteAsync())
- await using (StreamWriter writer = new StreamWriter(stream))
- {
- await writer.WriteLineAsync("Hello, World!");
- }
-
- Console.WriteLine($"Reading contents of '{file.FullName}'");
- using (StreamReader reader = await file.OpenTextAsync())
- {
- Console.WriteLine(await reader.ReadToEndAsync());
- }
-
- // Replace content of the file with data from an existing file
- await using FileStream input = File.OpenRead(@"D:\sample\hello-world.txt");
- await file.WriteAsync(input, overwrite: true);
-
- Console.WriteLine($"The file '{file.Name}' has a length of {await file.GetLengthAsync()} bytes");
-
- Console.WriteLine($"Deleting file '{file.FullName}'");
- await file.DeleteAsync();
- }
-}
-```
-
-The `VirtualFile` class provides the following properties to retrieve information about a file:
-- `FileSystem`: Gets the `IVirtualFileSystem` associated with the file.
-- `Directory`: Gets the `VirtualDirectory` representing the parent directory.
-- `DirectoryName`: Gets the full path of the parent directory.
-- `IsReadOnly`: Indicates whether the file is read-only.
-- `Name`: Gets the name of the file.
-- `Extension`: Gets the extension of the file.
-
-The `VirtualFile` class also exposes a `GetPropertiesAsync` method to retrieve file properties:
-- `Length`: Gets the size of the file in bytes.
-- `Exists`: Indicates whether the file exists.
-- `CreationTime`: Gets the creation time of the file.
-- `LastAccessTime`: Gets the last access time of the file.
-- `LastWriteTime`: Gets the last modification time of the file.
-
-For convenience, many methods specific to `VirtualFile` are also available as extension methods on `IVirtualFileSystem`.
-
-For example, if we know the path of the file we want to read, we don't need to obtain a `VirtualFile` object explicitly:
-```csharp
-using StreamReader reader = await fs.OpenTextAsync("/docs/README", Encoding.UTF8);
-Console.WriteLine(await reader.ReadToEndAsync());
-```
-
-### VirtualDirectory
-
-The `VirtualDirectory` class provides properties and methods for creating, deleting, and enumerating directories and subdirectories.
-
-```csharp
-public static async Task PrintFilesAsync(VirtualDirectory directory, string padding = "", CancellationToken cancellationToken = default)
-{
- await foreach (VirtualNode node in directory.GetFileNodesAsync(cancellationToken))
- {
- Console.WriteLine($"{padding}{node.Name}");
-
- if (node is VirtualDirectory dir)
- {
- await PrintFilesAsync(dir, padding + " ", cancellationToken);
- }
- }
-}
-
-// Print the file tree
-await PrintFilesAsync(fs.GetDirectory("/sample"));
-```
-
-The `VirtualDirectory` class provides the following properties to retrieve information about a directory:
-- `FileSystem`: Gets the `IVirtualFileSystem` associated with the directory.
-- `IsRoot`: Indicates whether the directory is the root directory.
-- `Parent`: Gets the `VirtualDirectory` representing the parent directory.
-- `DirectoryName`: Gets the full path of the parent directory.
-- `IsReadOnly`: Indicates whether the directory is read-only.
-- `Name`: Gets the name of the directory.
-
-The `VirtualDirectory` class also exposes a `GetPropertiesAsync` method to retrieve directory properties, if available:
-- `Exists`: Indicates whether the directory exists.
-- `CreationTime`: Gets the creation time of the directory.
-- `LastAccessTime`: Gets the last access time of the directory.
-- `LastWriteTime`: Gets the last modification time of the directory.
-- `Length`: Always returns `0` for directories.
-
-For convenience, many methods specific to `VirtualDirectory` are also available as extension methods on `IVirtualFileSystem`.
-
-For example, if we know the path of the desired directory, we don't need to obtain a `VirtualDirectory` object explicitly:
-```csharp
-await foreach (VirtualFile file in fs.GetFilesAsync("/sample/directory"))
-{
- Console.WriteLine($"Processing: {file.FullName}");
-}
-
-// Delete the directory recursively
-await fs.DeleteDirectoryAsync("/sample/directory");
-```
-
-## Remarks
-
-The file system in use may be read-only, and as a result, any modifying operations on files and directories will throw an exception.
-To check if the file system is read-only, the `IVirtualFileSystem`, `VirtualFile`, and `VirtualDirectory` classes provide the `IsReadOnly` property.
-
-```csharp
-if (!fs.IsReadOnly)
-{
- await fs.DeleteFileAsync("/hello.txt");
-}
-```
-
-## Related Projects
-- [Ramstack.FileSystem.Physical](https://www.nuget.org/packages/Ramstack.FileSystem.Physical) - Provides an implementation based on the local file system.
-- [Ramstack.FileSystem.Azure](https://www.nuget.org/packages/Ramstack.FileSystem.Azure) - Provides an implementation using Azure Blob Storage.
-- [Ramstack.FileSystem.Amazon](https://www.nuget.org/packages/Ramstack.FileSystem.Amazon) - Provides an implementation using Amazon S3 storage.
-- [Ramstack.FileSystem.Google](https://www.nuget.org/packages/Ramstack.FileSystem.Google) - Provides an implementation using Google Cloud Storage.
-- [Ramstack.FileSystem.Readonly](https://www.nuget.org/packages/Ramstack.FileSystem.Readonly) - Provides a read-only wrapper for the underlying file system.
-- [Ramstack.FileSystem.Globbing](https://www.nuget.org/packages/Ramstack.FileSystem.Globbing) - Wraps the file system, filtering files and directories using glob patterns.
-- [Ramstack.FileSystem.Prefixed](https://www.nuget.org/packages/Ramstack.FileSystem.Prefixed) - Adds a prefix to file paths within the underlying file system.
-- [Ramstack.FileSystem.Sub](https://www.nuget.org/packages/Ramstack.FileSystem.Sub) - Wraps the underlying file system, restricting access to a specific subpath.
-- [Ramstack.FileSystem.Adapters](https://www.nuget.org/packages/Ramstack.FileSystem.Adapters) - Provides integration with `Microsoft.Extensions.FileProviders`.
-- [Ramstack.FileSystem.Composite](https://www.nuget.org/packages/Ramstack.FileSystem.Composite) - Provides an implementation that combines multiple file systems into a single composite file system.
-
-## Supported Versions
-
-| | Version |
-|------|----------------|
-| .NET | 6, 7, 8, 9, 10 |
-
-## Contributions
-
-Bug reports and contributions are welcome.
-
-## License
-
-This package is released as open source under the **MIT License**. See the [LICENSE](LICENSE) file for more details.
+# Ramstack.FileSystem.Abstractions
+[](https://nuget.org/packages/Ramstack.FileSystem.Abstractions)
+[](https://github.com/rameel/ramstack.filesystem/blob/main/LICENSE)
+
+Provides a virtual file system abstraction.
+
+**For current implementations, see [Related Projects](#related-projects)**
+
+## Getting Started
+
+To install the `Ramstack.FileSystem.Abstractions` [NuGet package](https://www.nuget.org/packages/Ramstack.FileSystem.Abstractions)
+in your project, run the following command:
+
+```bash
+dotnet add package Ramstack.FileSystem.Abstractions
+```
+
+## Overview
+
+The primary interface is `IVirtualFileSystem`, which exposes methods to:
+- Access files (`VirtualFile`).
+- Access directories (`VirtualDirectory`).
+
+### VirtualFile
+
+The `VirtualFile` class provides properties and methods for creating, deleting, copying, and opening files within the virtual file system.
+
+```csharp
+using Ramstack.FileSystem;
+
+public class Program
+{
+ public static async Task Sample(IVirtualFileSystem fs)
+ {
+ var file = fs.GetFile("/sample/hello.txt");
+
+ Console.WriteLine($"Writing 'Hello, World!' to '{file.FullName}'");
+
+ await using (Stream stream = await file.OpenWriteAsync())
+ await using (StreamWriter writer = new StreamWriter(stream))
+ {
+ await writer.WriteLineAsync("Hello, World!");
+ }
+
+ Console.WriteLine($"Reading contents of '{file.FullName}'");
+ using (StreamReader reader = await file.OpenTextAsync())
+ {
+ Console.WriteLine(await reader.ReadToEndAsync());
+ }
+
+ // Replace content of the file with data from an existing file
+ await using FileStream input = File.OpenRead(@"D:\sample\hello-world.txt");
+ await file.WriteAsync(input, overwrite: true);
+
+ Console.WriteLine($"The file '{file.Name}' has a length of {await file.GetLengthAsync()} bytes");
+
+ Console.WriteLine($"Deleting file '{file.FullName}'");
+ await file.DeleteAsync();
+ }
+}
+```
+
+The `VirtualFile` class provides the following properties to retrieve information about a file:
+- `FileSystem`: Gets the `IVirtualFileSystem` associated with the file.
+- `Directory`: Gets the `VirtualDirectory` representing the parent directory.
+- `DirectoryName`: Gets the full path of the parent directory.
+- `IsReadOnly`: Indicates whether the file is read-only.
+- `Name`: Gets the name of the file.
+- `Extension`: Gets the extension of the file.
+
+The `VirtualFile` class also exposes a `GetPropertiesAsync` method to retrieve file properties:
+- `Length`: Gets the size of the file in bytes.
+- `Exists`: Indicates whether the file exists.
+- `CreationTime`: Gets the creation time of the file.
+- `LastAccessTime`: Gets the last access time of the file.
+- `LastWriteTime`: Gets the last modification time of the file.
+
+For convenience, many methods specific to `VirtualFile` are also available as extension methods on `IVirtualFileSystem`.
+
+For example, if we know the path of the file we want to read, we don't need to obtain a `VirtualFile` object explicitly:
+```csharp
+using StreamReader reader = await fs.OpenTextAsync("/docs/README", Encoding.UTF8);
+Console.WriteLine(await reader.ReadToEndAsync());
+```
+
+### VirtualDirectory
+
+The `VirtualDirectory` class provides properties and methods for creating, deleting, and enumerating directories and subdirectories.
+
+```csharp
+public static async Task PrintFilesAsync(VirtualDirectory directory, string padding = "", CancellationToken cancellationToken = default)
+{
+ await foreach (VirtualNode node in directory.GetFileNodesAsync(cancellationToken))
+ {
+ Console.WriteLine($"{padding}{node.Name}");
+
+ if (node is VirtualDirectory dir)
+ {
+ await PrintFilesAsync(dir, padding + " ", cancellationToken);
+ }
+ }
+}
+
+// Print the file tree
+await PrintFilesAsync(fs.GetDirectory("/sample"));
+```
+
+The `VirtualDirectory` class provides the following properties to retrieve information about a directory:
+- `FileSystem`: Gets the `IVirtualFileSystem` associated with the directory.
+- `IsRoot`: Indicates whether the directory is the root directory.
+- `Parent`: Gets the `VirtualDirectory` representing the parent directory.
+- `DirectoryName`: Gets the full path of the parent directory.
+- `IsReadOnly`: Indicates whether the directory is read-only.
+- `Name`: Gets the name of the directory.
+
+The `VirtualDirectory` class also exposes a `GetPropertiesAsync` method to retrieve directory properties, if available:
+- `Exists`: Indicates whether the directory exists.
+- `CreationTime`: Gets the creation time of the directory.
+- `LastAccessTime`: Gets the last access time of the directory.
+- `LastWriteTime`: Gets the last modification time of the directory.
+- `Length`: Always returns `0` for directories.
+
+For convenience, many methods specific to `VirtualDirectory` are also available as extension methods on `IVirtualFileSystem`.
+
+For example, if we know the path of the desired directory, we don't need to obtain a `VirtualDirectory` object explicitly:
+```csharp
+await foreach (VirtualFile file in fs.GetFilesAsync("/sample/directory"))
+{
+ Console.WriteLine($"Processing: {file.FullName}");
+}
+
+// Delete the directory recursively
+await fs.DeleteDirectoryAsync("/sample/directory");
+```
+
+## Remarks
+
+The file system in use may be read-only, and as a result, any modifying operations on files and directories will throw an exception.
+To check if the file system is read-only, the `IVirtualFileSystem`, `VirtualFile`, and `VirtualDirectory` classes provide the `IsReadOnly` property.
+
+```csharp
+if (!fs.IsReadOnly)
+{
+ await fs.DeleteFileAsync("/hello.txt");
+}
+```
+
+## Related Projects
+- [Ramstack.FileSystem.Physical](https://www.nuget.org/packages/Ramstack.FileSystem.Physical) - Provides an implementation based on the local file system.
+- [Ramstack.FileSystem.Azure](https://www.nuget.org/packages/Ramstack.FileSystem.Azure) - Provides an implementation using Azure Blob Storage.
+- [Ramstack.FileSystem.Amazon](https://www.nuget.org/packages/Ramstack.FileSystem.Amazon) - Provides an implementation using Amazon S3 storage.
+- [Ramstack.FileSystem.Google](https://www.nuget.org/packages/Ramstack.FileSystem.Google) - Provides an implementation using Google Cloud Storage.
+- [Ramstack.FileSystem.Readonly](https://www.nuget.org/packages/Ramstack.FileSystem.Readonly) - Provides a read-only wrapper for the underlying file system.
+- [Ramstack.FileSystem.Globbing](https://www.nuget.org/packages/Ramstack.FileSystem.Globbing) - Wraps the file system, filtering files and directories using glob patterns.
+- [Ramstack.FileSystem.Prefixed](https://www.nuget.org/packages/Ramstack.FileSystem.Prefixed) - Adds a prefix to file paths within the underlying file system.
+- [Ramstack.FileSystem.Sub](https://www.nuget.org/packages/Ramstack.FileSystem.Sub) - Wraps the underlying file system, restricting access to a specific subpath.
+- [Ramstack.FileSystem.Adapters](https://www.nuget.org/packages/Ramstack.FileSystem.Adapters) - Provides integration with `Microsoft.Extensions.FileProviders`.
+- [Ramstack.FileSystem.Composite](https://www.nuget.org/packages/Ramstack.FileSystem.Composite) - Provides an implementation that combines multiple file systems into a single composite file system.
+
+## Supported Versions
+
+| | Version |
+|------|----------------|
+| .NET | 6, 7, 8, 9, 10 |
+
+## Contributions
+
+Bug reports and contributions are welcome.
+
+## License
+
+This package is released as open source under the **MIT License**. See the [LICENSE](LICENSE) file for more details.
diff --git a/src/Ramstack.FileSystem.Abstractions/Ramstack.FileSystem.Abstractions.csproj b/src/Ramstack.FileSystem.Abstractions/Ramstack.FileSystem.Abstractions.csproj
index 51eb9b4..6f50f5e 100644
--- a/src/Ramstack.FileSystem.Abstractions/Ramstack.FileSystem.Abstractions.csproj
+++ b/src/Ramstack.FileSystem.Abstractions/Ramstack.FileSystem.Abstractions.csproj
@@ -1,64 +1,64 @@
-
-
-
- net6.0;net8.0
- A .NET library that provides a virtual file system abstraction.
- enable
- enable
- preview
- true
- true
- Ramstack.FileSystem
-
-
-
- 649;1591
- CS8600;CS8601;CS8602;CS8603;CS8604;CS8610;CS8613;CS8614;CS8618;CS8619;CS8620;CS8625;CS8643;CS8653;CS8714
-
-
-
- https://github.com/rameel/ramstack.filesystem
- Rameel
- filesystem;fileprovider
- https://github.com/rameel/ramstack.filesystem#readme
- README.md
- MIT
- true
- snupkg
- true
- true
- true
- true
-
-
-
- true
-
-
-
-
-
-
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
-
- Properties\README.md
- True
- \
-
-
-
-
+
+
+
+ net6.0;net8.0
+ A .NET library that provides a virtual file system abstraction.
+ enable
+ enable
+ preview
+ true
+ true
+ Ramstack.FileSystem
+
+
+
+ 649;1591
+ CS8600;CS8601;CS8602;CS8603;CS8604;CS8610;CS8613;CS8614;CS8618;CS8619;CS8620;CS8625;CS8643;CS8653;CS8714
+
+
+
+ https://github.com/rameel/ramstack.filesystem
+ Rameel
+ filesystem;fileprovider
+ https://github.com/rameel/ramstack.filesystem#readme
+ README.md
+ MIT
+ true
+ snupkg
+ true
+ true
+ true
+ true
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+ Properties\README.md
+ True
+ \
+
+
+
+
diff --git a/src/Ramstack.FileSystem.Abstractions/Utilities/EnumerableExtensions.cs b/src/Ramstack.FileSystem.Abstractions/Utilities/EnumerableExtensions.cs
index 613dce0..5cbe63d 100644
--- a/src/Ramstack.FileSystem.Abstractions/Utilities/EnumerableExtensions.cs
+++ b/src/Ramstack.FileSystem.Abstractions/Utilities/EnumerableExtensions.cs
@@ -1,64 +1,64 @@
-namespace Ramstack.FileSystem.Utilities;
-
-///
-/// Provides extension methods for the .
-///
-internal static class EnumerableExtensions
-{
- ///
- /// Converts an enumerable sequence to an async-enumerable sequence.
- ///
- /// The type of the elements in the source sequence.
- /// The enumerable sequence to convert to an async-enumerable sequence.
- ///
- /// The async-enumerable sequence whose elements are pulled from the given enumerable sequence.
- ///
- public static IAsyncEnumerable ToAsyncEnumerable(this IEnumerable source) =>
- new AsyncEnumerableAdapter(source);
-
- #region Inner type: AsyncEnumerableAdapter
-
- ///
- /// Wraps the to the .
- ///
- /// The type of the elements in the collection.
- /// The to wrap.
- private sealed class AsyncEnumerableAdapter(IEnumerable source) : IAsyncEnumerable
- {
- ///
- public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken) =>
- new AsyncEnumeratorAdapter(source.GetEnumerator(), cancellationToken);
- }
-
- #endregion
-
- #region Inner type: AsyncEnumeratorAdapter
-
- ///
- /// Wraps the to the .
- ///
- /// The type of the elements in the collection.
- /// The to wrap.
- /// An optional cancellation token to cancel the operation.
- private sealed class AsyncEnumeratorAdapter(IEnumerator enumerator, CancellationToken cancellationToken) : IAsyncEnumerator
- {
- ///
- public T Current => enumerator.Current;
-
- ///
- public ValueTask MoveNextAsync()
- {
- cancellationToken.ThrowIfCancellationRequested();
- return new ValueTask(enumerator.MoveNext());
- }
-
- ///
- public ValueTask DisposeAsync()
- {
- enumerator.Dispose();
- return default;
- }
- }
-
- #endregion
-}
+namespace Ramstack.FileSystem.Utilities;
+
+///
+/// Provides extension methods for the .
+///
+internal static class EnumerableExtensions
+{
+ ///
+ /// Converts an enumerable sequence to an async-enumerable sequence.
+ ///
+ /// The type of the elements in the source sequence.
+ /// The enumerable sequence to convert to an async-enumerable sequence.
+ ///
+ /// The async-enumerable sequence whose elements are pulled from the given enumerable sequence.
+ ///
+ public static IAsyncEnumerable ToAsyncEnumerable(this IEnumerable source) =>
+ new AsyncEnumerableAdapter(source);
+
+ #region Inner type: AsyncEnumerableAdapter
+
+ ///
+ /// Wraps the to the .
+ ///
+ /// The type of the elements in the collection.
+ /// The to wrap.
+ private sealed class AsyncEnumerableAdapter(IEnumerable source) : IAsyncEnumerable
+ {
+ ///
+ public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken) =>
+ new AsyncEnumeratorAdapter(source.GetEnumerator(), cancellationToken);
+ }
+
+ #endregion
+
+ #region Inner type: AsyncEnumeratorAdapter
+
+ ///
+ /// Wraps the to the .
+ ///
+ /// The type of the elements in the collection.
+ /// The to wrap.
+ /// An optional cancellation token to cancel the operation.
+ private sealed class AsyncEnumeratorAdapter(IEnumerator enumerator, CancellationToken cancellationToken) : IAsyncEnumerator
+ {
+ ///
+ public T Current => enumerator.Current;
+
+ ///
+ public ValueTask MoveNextAsync()
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return new ValueTask(enumerator.MoveNext());
+ }
+
+ ///
+ public ValueTask DisposeAsync()
+ {
+ enumerator.Dispose();
+ return default;
+ }
+ }
+
+ #endregion
+}
diff --git a/src/Ramstack.FileSystem.Abstractions/Utilities/PathTokenizer.cs b/src/Ramstack.FileSystem.Abstractions/Utilities/PathTokenizer.cs
index 879a53d..8bd3ca1 100644
--- a/src/Ramstack.FileSystem.Abstractions/Utilities/PathTokenizer.cs
+++ b/src/Ramstack.FileSystem.Abstractions/Utilities/PathTokenizer.cs
@@ -1,94 +1,94 @@
-namespace Ramstack.FileSystem.Utilities;
-
-///
-/// Tokenizes a file path into its constituent components.
-///
-/// The path of the file to tokenize.
-internal readonly struct PathTokenizer(string path)
-{
- ///
- /// Returns an enumerator that iterates through the collection.
- ///
- ///
- /// An enumerator to iterate through the collection.
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Enumerator GetEnumerator() =>
- new Enumerator(path);
-
- ///
- /// Tokenizes the specified path into a collection of path components.
- ///
- /// The file path to tokenize.
- ///
- /// The .
- ///
- public static PathTokenizer Tokenize(string path) =>
- new PathTokenizer(path);
-
- #region Inner type: Enumerator
-
- ///
- /// Represents the enumerator.
- ///
- public struct Enumerator
- {
- private readonly string _path;
- private int _start;
- private int _count;
-
- ///
- /// Initializes a new instance of the structure.
- ///
- /// The file path to tokenize.
- internal Enumerator(string path) =>
- (_path, _count) = (path, -1);
-
- ///
- /// Gets the current path component.
- ///
- public ReadOnlySpan Current
- {
- //
- // Using AsSpan(_start) followed by slicing is more efficient
- // than AsSpan(_start, _count) because:
- // 1) MoveNext already validated _start bounds
- // 2) We only need to check _count <= length (simpler than checking start+count)
- //
- // The alternative AsSpan(start, count) does a combined bounds check
- // which the JIT can't optimize away:
- // (ulong)(uint)_start + (ulong)(uint)_count <= (ulong)(uint)Length
- //
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => _path.AsSpan(_start)[.._count];
- }
-
- ///
- /// Advances the enumerator to the next path component.
- ///
- ///
- /// if the enumerator was successfully advanced
- /// to the next path component; otherwise, .
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public bool MoveNext()
- {
- _start = _start + _count + 1;
-
- if ((uint)_start < (uint)_path.Length)
- {
- var s = _path.AsSpan(_start);
-
- _count = s.IndexOfAny('/', '\\');
- if (_count < 0)
- _count = s.Length;
-
- return true;
- }
-
- return false;
- }
- }
-
- #endregion
-}
+namespace Ramstack.FileSystem.Utilities;
+
+///
+/// Tokenizes a file path into its constituent components.
+///
+/// The path of the file to tokenize.
+internal readonly struct PathTokenizer(string path)
+{
+ ///
+ /// Returns an enumerator that iterates through the collection.
+ ///
+ ///
+ /// An enumerator to iterate through the collection.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Enumerator GetEnumerator() =>
+ new Enumerator(path);
+
+ ///
+ /// Tokenizes the specified path into a collection of path components.
+ ///
+ /// The file path to tokenize.
+ ///
+ /// The .
+ ///
+ public static PathTokenizer Tokenize(string path) =>
+ new PathTokenizer(path);
+
+ #region Inner type: Enumerator
+
+ ///
+ /// Represents the enumerator.
+ ///
+ public struct Enumerator
+ {
+ private readonly string _path;
+ private int _start;
+ private int _count;
+
+ ///
+ /// Initializes a new instance of the structure.
+ ///
+ /// The file path to tokenize.
+ internal Enumerator(string path) =>
+ (_path, _count) = (path, -1);
+
+ ///
+ /// Gets the current path component.
+ ///
+ public ReadOnlySpan Current
+ {
+ //
+ // Using AsSpan(_start) followed by slicing is more efficient
+ // than AsSpan(_start, _count) because:
+ // 1) MoveNext already validated _start bounds
+ // 2) We only need to check _count <= length (simpler than checking start+count)
+ //
+ // The alternative AsSpan(start, count) does a combined bounds check
+ // which the JIT can't optimize away:
+ // (ulong)(uint)_start + (ulong)(uint)_count <= (ulong)(uint)Length
+ //
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _path.AsSpan(_start)[.._count];
+ }
+
+ ///
+ /// Advances the enumerator to the next path component.
+ ///
+ ///
+ /// if the enumerator was successfully advanced
+ /// to the next path component; otherwise, .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool MoveNext()
+ {
+ _start = _start + _count + 1;
+
+ if ((uint)_start < (uint)_path.Length)
+ {
+ var s = _path.AsSpan(_start);
+
+ _count = s.IndexOfAny('/', '\\');
+ if (_count < 0)
+ _count = s.Length;
+
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ #endregion
+}
diff --git a/src/Ramstack.FileSystem.Abstractions/VirtualDirectory.cs b/src/Ramstack.FileSystem.Abstractions/VirtualDirectory.cs
index f9f2dfc..55888a7 100644
--- a/src/Ramstack.FileSystem.Abstractions/VirtualDirectory.cs
+++ b/src/Ramstack.FileSystem.Abstractions/VirtualDirectory.cs
@@ -1,289 +1,289 @@
-using Ramstack.Globbing;
-using Ramstack.Globbing.Traversal;
-
-namespace Ramstack.FileSystem;
-
-///
-/// Represents a virtual directory within a specified file system.
-///
-[DebuggerDisplay("{FullName,nq}")]
-public abstract class VirtualDirectory : VirtualNode
-{
- ///
- /// Gets a value indicating whether the current directory is the root directory.
- ///
- public bool IsRoot => FullName == "/";
-
- ///
- /// Gets a instance representing the parent directory.
- ///
- public VirtualDirectory? Parent
- {
- get
- {
- if (IsRoot)
- return null;
-
- var directoryName = VirtualPath.GetDirectoryName(FullName);
- return FileSystem.GetDirectory(directoryName);
- }
- }
-
- ///
- /// Initializes a new instance of the class with the specified path.
- ///
- /// The full path of the directory.
- protected VirtualDirectory(string path) : base(path)
- {
- }
-
- ///
- /// Initializes a new instance of the class with the specified path and properties.
- ///
- /// The full path of the directory.
- /// The properties of the directory.
- protected VirtualDirectory(string path, VirtualNodeProperties? properties) : base(path, properties)
- {
- }
-
- ///
- /// Asynchronously creates the current directory.
- ///
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- public ValueTask CreateAsync(CancellationToken cancellationToken = default)
- {
- EnsureWritable();
- Refresh();
-
- return CreateCoreAsync(cancellationToken);
- }
-
- ///
- /// Asynchronously deletes the current directory, including all its subdirectories and files.
- ///
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- public ValueTask DeleteAsync(CancellationToken cancellationToken = default)
- {
- EnsureWritable();
- Refresh();
-
- return DeleteCoreAsync(cancellationToken);
- }
-
- ///
- /// Asynchronously returns an async-enumerable collection of file nodes (both directories and files) within the current directory.
- ///
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- public IAsyncEnumerable GetFileNodesAsync(CancellationToken cancellationToken = default) =>
- GetFileNodesCoreAsync(cancellationToken);
-
- ///
- /// Asynchronously returns an async-enumerable collection of files within the current directory.
- ///
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- public IAsyncEnumerable GetFilesAsync(CancellationToken cancellationToken = default) =>
- GetFilesCoreAsync(cancellationToken);
-
- ///
- /// Asynchronously returns an async-enumerable collection of directories within the current directory.
- ///
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- public IAsyncEnumerable GetDirectoriesAsync(CancellationToken cancellationToken = default) =>
- GetDirectoriesCoreAsync(cancellationToken);
-
- ///
- /// Asynchronously returns an async-enumerable collection of file nodes (both directories and files)
- /// within the current directory that match any of the specified glob patterns.
- ///
- /// An array of glob patterns to match against the names of file nodes.
- /// An optional array of glob patterns to exclude file nodes.
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- public IAsyncEnumerable GetFileNodesAsync(string[] patterns, string[]? excludes, CancellationToken cancellationToken = default)
- {
- ArgumentNullException.ThrowIfNull(patterns);
- return GetFileNodesCoreAsync(patterns, excludes, cancellationToken);
- }
-
- ///
- /// Asynchronously returns an async-enumerable collection of files within the current directory
- /// that match any of the specified glob patterns.
- ///
- /// An array of glob patterns to match against the names of files.
- /// An optional array of glob patterns to exclude files.
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- public IAsyncEnumerable GetFilesAsync(string[] patterns, string[]? excludes, CancellationToken cancellationToken = default)
- {
- ArgumentNullException.ThrowIfNull(patterns);
- return GetFilesCoreAsync(patterns, excludes, cancellationToken);
- }
-
- ///
- /// Asynchronously returns an async-enumerable collection of directories within the current directory
- /// that match any of the specified glob patterns.
- ///
- /// An array of glob patterns to match against the names of directories.
- /// An optional array of glob patterns to exclude directories.
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- public IAsyncEnumerable GetDirectoriesAsync(string[] patterns, string[]? excludes, CancellationToken cancellationToken = default)
- {
- ArgumentNullException.ThrowIfNull(patterns);
- return GetDirectoriesCoreAsync(patterns, excludes, cancellationToken);
- }
-
- ///
- /// Core implementation for asynchronously creating the current directory.
- ///
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- protected abstract ValueTask CreateCoreAsync(CancellationToken cancellationToken);
-
- ///
- /// Core implementation for asynchronously deleting the current directory, including all its subdirectories and files.
- ///
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- protected abstract ValueTask DeleteCoreAsync(CancellationToken cancellationToken);
-
- ///
- /// Core implementation for asynchronously returning an async-enumerable collection of file nodes (both directories and files) within the current directory.
- ///
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- protected abstract IAsyncEnumerable GetFileNodesCoreAsync(CancellationToken cancellationToken);
-
- ///
- /// Core implementation for asynchronously returning an async-enumerable collection of files within the current directory.
- ///
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- protected virtual async IAsyncEnumerable GetFilesCoreAsync([EnumeratorCancellation] CancellationToken cancellationToken)
- {
- await foreach (var node in GetFileNodesCoreAsync(cancellationToken).ConfigureAwait(false))
- {
- if (node is VirtualFile file)
- yield return file;
- }
- }
-
- ///
- /// Core implementation for asynchronously returning an async-enumerable collection of directories within the current directory.
- ///
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- protected virtual async IAsyncEnumerable GetDirectoriesCoreAsync([EnumeratorCancellation] CancellationToken cancellationToken)
- {
- await foreach (var node in GetFileNodesCoreAsync(cancellationToken).ConfigureAwait(false))
- {
- if (node is VirtualDirectory directory)
- yield return directory;
- }
- }
-
- ///
- /// 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.
- ///
- /// An array of glob patterns to match against the names of file nodes.
- /// An optional array of glob patterns to exclude file nodes.
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- protected virtual IAsyncEnumerable GetFileNodesCoreAsync(string[] patterns, string[]? excludes, CancellationToken cancellationToken)
- {
- return new FileTreeAsyncEnumerable(this, cancellationToken)
- {
- Patterns = patterns,
- Excludes = excludes ?? [],
- Flags = MatchFlags.Unix,
- FileNameSelector = node => node.Name,
- ShouldRecursePredicate = node => node is VirtualDirectory,
- ChildrenSelector = (node, token) => ((VirtualDirectory)node).GetFileNodesCoreAsync(token),
- ResultSelector = node => node
- };
- }
-
- ///
- /// Core implementation for asynchronously returning an async-enumerable collection of files within the current directory
- /// that match any of the specified glob patterns.
- ///
- /// An array of glob patterns to match against the names of files.
- /// An optional array of glob patterns to exclude files.
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- protected virtual IAsyncEnumerable GetFilesCoreAsync(string[] patterns, string[]? excludes, CancellationToken cancellationToken)
- {
- return new FileTreeAsyncEnumerable(this, cancellationToken)
- {
- Patterns = patterns,
- Excludes = excludes ?? [],
- Flags = MatchFlags.Unix,
- 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
- };
- }
-
- ///
- /// Core implementation for asynchronously returning an async-enumerable collection of directories within the current directory
- /// that match any of the specified glob patterns.
- ///
- /// An array of glob patterns to match against the names of directories.
- /// An optional array of glob patterns to exclude directories.
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- protected virtual IAsyncEnumerable GetDirectoriesCoreAsync(string[] patterns, string[]? excludes, CancellationToken cancellationToken)
- {
- return new FileTreeAsyncEnumerable(this, cancellationToken)
- {
- Patterns = patterns,
- Excludes = excludes ?? [],
- Flags = MatchFlags.Unix,
- 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
- };
- }
-}
+using Ramstack.Globbing;
+using Ramstack.Globbing.Traversal;
+
+namespace Ramstack.FileSystem;
+
+///
+/// Represents a virtual directory within a specified file system.
+///
+[DebuggerDisplay("{FullName,nq}")]
+public abstract class VirtualDirectory : VirtualNode
+{
+ ///
+ /// Gets a value indicating whether the current directory is the root directory.
+ ///
+ public bool IsRoot => FullName == "/";
+
+ ///
+ /// Gets a instance representing the parent directory.
+ ///
+ public VirtualDirectory? Parent
+ {
+ get
+ {
+ if (IsRoot)
+ return null;
+
+ var directoryName = VirtualPath.GetDirectoryName(FullName);
+ return FileSystem.GetDirectory(directoryName);
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class with the specified path.
+ ///
+ /// The full path of the directory.
+ protected VirtualDirectory(string path) : base(path)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with the specified path and properties.
+ ///
+ /// The full path of the directory.
+ /// The properties of the directory.
+ protected VirtualDirectory(string path, VirtualNodeProperties? properties) : base(path, properties)
+ {
+ }
+
+ ///
+ /// Asynchronously creates the current directory.
+ ///
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ public ValueTask CreateAsync(CancellationToken cancellationToken = default)
+ {
+ EnsureWritable();
+ Refresh();
+
+ return CreateCoreAsync(cancellationToken);
+ }
+
+ ///
+ /// Asynchronously deletes the current directory, including all its subdirectories and files.
+ ///
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ public ValueTask DeleteAsync(CancellationToken cancellationToken = default)
+ {
+ EnsureWritable();
+ Refresh();
+
+ return DeleteCoreAsync(cancellationToken);
+ }
+
+ ///
+ /// Asynchronously returns an async-enumerable collection of file nodes (both directories and files) within the current directory.
+ ///
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ public IAsyncEnumerable GetFileNodesAsync(CancellationToken cancellationToken = default) =>
+ GetFileNodesCoreAsync(cancellationToken);
+
+ ///
+ /// Asynchronously returns an async-enumerable collection of files within the current directory.
+ ///
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ public IAsyncEnumerable GetFilesAsync(CancellationToken cancellationToken = default) =>
+ GetFilesCoreAsync(cancellationToken);
+
+ ///
+ /// Asynchronously returns an async-enumerable collection of directories within the current directory.
+ ///
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ public IAsyncEnumerable GetDirectoriesAsync(CancellationToken cancellationToken = default) =>
+ GetDirectoriesCoreAsync(cancellationToken);
+
+ ///
+ /// Asynchronously returns an async-enumerable collection of file nodes (both directories and files)
+ /// within the current directory that match any of the specified glob patterns.
+ ///
+ /// An array of glob patterns to match against the names of file nodes.
+ /// An optional array of glob patterns to exclude file nodes.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ public IAsyncEnumerable GetFileNodesAsync(string[] patterns, string[]? excludes, CancellationToken cancellationToken = default)
+ {
+ ArgumentNullException.ThrowIfNull(patterns);
+ return GetFileNodesCoreAsync(patterns, excludes, cancellationToken);
+ }
+
+ ///
+ /// Asynchronously returns an async-enumerable collection of files within the current directory
+ /// that match any of the specified glob patterns.
+ ///
+ /// An array of glob patterns to match against the names of files.
+ /// An optional array of glob patterns to exclude files.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ public IAsyncEnumerable GetFilesAsync(string[] patterns, string[]? excludes, CancellationToken cancellationToken = default)
+ {
+ ArgumentNullException.ThrowIfNull(patterns);
+ return GetFilesCoreAsync(patterns, excludes, cancellationToken);
+ }
+
+ ///
+ /// Asynchronously returns an async-enumerable collection of directories within the current directory
+ /// that match any of the specified glob patterns.
+ ///
+ /// An array of glob patterns to match against the names of directories.
+ /// An optional array of glob patterns to exclude directories.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ public IAsyncEnumerable GetDirectoriesAsync(string[] patterns, string[]? excludes, CancellationToken cancellationToken = default)
+ {
+ ArgumentNullException.ThrowIfNull(patterns);
+ return GetDirectoriesCoreAsync(patterns, excludes, cancellationToken);
+ }
+
+ ///
+ /// Core implementation for asynchronously creating the current directory.
+ ///
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ protected abstract ValueTask CreateCoreAsync(CancellationToken cancellationToken);
+
+ ///
+ /// Core implementation for asynchronously deleting the current directory, including all its subdirectories and files.
+ ///
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ protected abstract ValueTask DeleteCoreAsync(CancellationToken cancellationToken);
+
+ ///
+ /// Core implementation for asynchronously returning an async-enumerable collection of file nodes (both directories and files) within the current directory.
+ ///
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ protected abstract IAsyncEnumerable GetFileNodesCoreAsync(CancellationToken cancellationToken);
+
+ ///
+ /// Core implementation for asynchronously returning an async-enumerable collection of files within the current directory.
+ ///
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ protected virtual async IAsyncEnumerable GetFilesCoreAsync([EnumeratorCancellation] CancellationToken cancellationToken)
+ {
+ await foreach (var node in GetFileNodesCoreAsync(cancellationToken).ConfigureAwait(false))
+ {
+ if (node is VirtualFile file)
+ yield return file;
+ }
+ }
+
+ ///
+ /// Core implementation for asynchronously returning an async-enumerable collection of directories within the current directory.
+ ///
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ protected virtual async IAsyncEnumerable GetDirectoriesCoreAsync([EnumeratorCancellation] CancellationToken cancellationToken)
+ {
+ await foreach (var node in GetFileNodesCoreAsync(cancellationToken).ConfigureAwait(false))
+ {
+ if (node is VirtualDirectory directory)
+ yield return directory;
+ }
+ }
+
+ ///
+ /// 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.
+ ///
+ /// An array of glob patterns to match against the names of file nodes.
+ /// An optional array of glob patterns to exclude file nodes.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ protected virtual IAsyncEnumerable GetFileNodesCoreAsync(string[] patterns, string[]? excludes, CancellationToken cancellationToken)
+ {
+ return new FileTreeAsyncEnumerable(this, cancellationToken)
+ {
+ Patterns = patterns,
+ Excludes = excludes ?? [],
+ Flags = MatchFlags.Unix,
+ FileNameSelector = node => node.Name,
+ ShouldRecursePredicate = node => node is VirtualDirectory,
+ ChildrenSelector = (node, token) => ((VirtualDirectory)node).GetFileNodesCoreAsync(token),
+ ResultSelector = node => node
+ };
+ }
+
+ ///
+ /// Core implementation for asynchronously returning an async-enumerable collection of files within the current directory
+ /// that match any of the specified glob patterns.
+ ///
+ /// An array of glob patterns to match against the names of files.
+ /// An optional array of glob patterns to exclude files.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ protected virtual IAsyncEnumerable GetFilesCoreAsync(string[] patterns, string[]? excludes, CancellationToken cancellationToken)
+ {
+ return new FileTreeAsyncEnumerable(this, cancellationToken)
+ {
+ Patterns = patterns,
+ Excludes = excludes ?? [],
+ Flags = MatchFlags.Unix,
+ 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
+ };
+ }
+
+ ///
+ /// Core implementation for asynchronously returning an async-enumerable collection of directories within the current directory
+ /// that match any of the specified glob patterns.
+ ///
+ /// An array of glob patterns to match against the names of directories.
+ /// An optional array of glob patterns to exclude directories.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ protected virtual IAsyncEnumerable GetDirectoriesCoreAsync(string[] patterns, string[]? excludes, CancellationToken cancellationToken)
+ {
+ return new FileTreeAsyncEnumerable(this, cancellationToken)
+ {
+ Patterns = patterns,
+ Excludes = excludes ?? [],
+ Flags = MatchFlags.Unix,
+ 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
+ };
+ }
+}
diff --git a/src/Ramstack.FileSystem.Abstractions/VirtualDirectoryExtensions.cs b/src/Ramstack.FileSystem.Abstractions/VirtualDirectoryExtensions.cs
index 800c0d6..268911b 100644
--- a/src/Ramstack.FileSystem.Abstractions/VirtualDirectoryExtensions.cs
+++ b/src/Ramstack.FileSystem.Abstractions/VirtualDirectoryExtensions.cs
@@ -1,138 +1,138 @@
-namespace Ramstack.FileSystem;
-
-///
-/// Provides extension methods for the class.
-///
-public static class VirtualDirectoryExtensions
-{
- ///
- /// Asynchronously returns an async-enumerable collection of file nodes (both directories and files)
- /// within the specified directory that match the given glob pattern.
- ///
- /// The representing the directory to search.
- /// The glob pattern to match against the names of file nodes.
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- public static IAsyncEnumerable GetFileNodesAsync(this VirtualDirectory directory, string pattern, CancellationToken cancellationToken = default) =>
- directory.GetFileNodesAsync(pattern, exclude: null, cancellationToken);
-
- ///
- /// Asynchronously returns an async-enumerable collection of file nodes (both directories and files)
- /// within the specified directory that match the given glob pattern.
- ///
- /// The representing the directory to search.
- /// The glob pattern to match against the names of file nodes.
- /// An optional glob pattern to exclude certain file nodes.
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- public static IAsyncEnumerable GetFileNodesAsync(this VirtualDirectory directory, string pattern, string? exclude, CancellationToken cancellationToken = default)
- {
- ArgumentNullException.ThrowIfNull(pattern);
-
- return directory.GetFileNodesAsync([pattern], exclude is not null ? [exclude] : null, cancellationToken);
- }
-
- ///
- /// Asynchronously returns an async-enumerable collection of file nodes (both directories and files)
- /// within the specified directory that match any of the given glob patterns.
- ///
- /// The representing the directory to search.
- /// An array of glob patterns to match against the names of file nodes.
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- public static IAsyncEnumerable GetFileNodesAsync(this VirtualDirectory directory, string[] patterns, CancellationToken cancellationToken = default) =>
- directory.GetFileNodesAsync(patterns, excludes: null, cancellationToken);
-
- ///
- /// Asynchronously returns an async-enumerable collection of files within the specified directory
- /// that match the given glob pattern.
- ///
- /// The representing the directory to search.
- /// The glob pattern to match against the names of files.
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- public static IAsyncEnumerable GetFilesAsync(this VirtualDirectory directory, string pattern, CancellationToken cancellationToken = default) =>
- directory.GetFilesAsync(pattern, exclude: null, cancellationToken);
-
- ///
- /// Asynchronously returns an async-enumerable collection of files within the specified directory
- /// that match the given glob pattern.
- ///
- /// The representing the directory to search.
- /// The glob pattern to match against the names of files.
- /// An optional glob pattern to exclude certain files.
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- public static IAsyncEnumerable GetFilesAsync(this VirtualDirectory directory, string pattern, string? exclude, CancellationToken cancellationToken = default)
- {
- ArgumentNullException.ThrowIfNull(pattern);
-
- return directory.GetFilesAsync([pattern], exclude is not null ? [exclude] : null, cancellationToken);
- }
-
- ///
- /// Asynchronously returns an async-enumerable collection of files within the specified directory
- /// that match any of the given glob patterns.
- ///
- /// The representing the directory to search.
- /// An array of glob patterns to match against the names of files.
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- public static IAsyncEnumerable GetFilesAsync(this VirtualDirectory directory, string[] patterns, CancellationToken cancellationToken = default) =>
- directory.GetFilesAsync(patterns, excludes: null, cancellationToken);
-
- ///
- /// Asynchronously returns an async-enumerable collection of directories within the specified directory
- /// that match the given glob pattern.
- ///
- /// The representing the directory to search.
- /// The glob pattern to match against the names of directories.
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- public static IAsyncEnumerable GetDirectoriesAsync(this VirtualDirectory directory, string pattern, CancellationToken cancellationToken = default) =>
- directory.GetDirectoriesAsync(pattern, exclude: null, cancellationToken);
-
- ///
- /// Asynchronously returns an async-enumerable collection of directories within the specified directory
- /// that match the given glob pattern.
- ///
- /// The representing the directory to search.
- /// The glob pattern to match against the names of directories.
- /// An optional glob pattern to exclude certain directories.
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- public static IAsyncEnumerable GetDirectoriesAsync(this VirtualDirectory directory, string pattern, string? exclude, CancellationToken cancellationToken = default)
- {
- ArgumentNullException.ThrowIfNull(pattern);
-
- return directory.GetDirectoriesAsync([pattern], exclude is not null ? [exclude] : null, cancellationToken);
- }
-
- ///
- /// Asynchronously returns an async-enumerable collection of directories within the specified directory
- /// that match any of the given glob patterns.
- ///
- /// The representing the directory to search.
- /// An array of glob patterns to match against the names of directories.
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- public static IAsyncEnumerable GetDirectoriesAsync(this VirtualDirectory directory, string[] patterns, CancellationToken cancellationToken = default) =>
- directory.GetDirectoriesAsync(patterns, excludes: null, cancellationToken);
-}
+namespace Ramstack.FileSystem;
+
+///
+/// Provides extension methods for the class.
+///
+public static class VirtualDirectoryExtensions
+{
+ ///
+ /// Asynchronously returns an async-enumerable collection of file nodes (both directories and files)
+ /// within the specified directory that match the given glob pattern.
+ ///
+ /// The representing the directory to search.
+ /// The glob pattern to match against the names of file nodes.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ public static IAsyncEnumerable GetFileNodesAsync(this VirtualDirectory directory, string pattern, CancellationToken cancellationToken = default) =>
+ directory.GetFileNodesAsync(pattern, exclude: null, cancellationToken);
+
+ ///
+ /// Asynchronously returns an async-enumerable collection of file nodes (both directories and files)
+ /// within the specified directory that match the given glob pattern.
+ ///
+ /// The representing the directory to search.
+ /// The glob pattern to match against the names of file nodes.
+ /// An optional glob pattern to exclude certain file nodes.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ public static IAsyncEnumerable GetFileNodesAsync(this VirtualDirectory directory, string pattern, string? exclude, CancellationToken cancellationToken = default)
+ {
+ ArgumentNullException.ThrowIfNull(pattern);
+
+ return directory.GetFileNodesAsync([pattern], exclude is not null ? [exclude] : null, cancellationToken);
+ }
+
+ ///
+ /// Asynchronously returns an async-enumerable collection of file nodes (both directories and files)
+ /// within the specified directory that match any of the given glob patterns.
+ ///
+ /// The representing the directory to search.
+ /// An array of glob patterns to match against the names of file nodes.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ public static IAsyncEnumerable GetFileNodesAsync(this VirtualDirectory directory, string[] patterns, CancellationToken cancellationToken = default) =>
+ directory.GetFileNodesAsync(patterns, excludes: null, cancellationToken);
+
+ ///
+ /// Asynchronously returns an async-enumerable collection of files within the specified directory
+ /// that match the given glob pattern.
+ ///
+ /// The representing the directory to search.
+ /// The glob pattern to match against the names of files.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ public static IAsyncEnumerable GetFilesAsync(this VirtualDirectory directory, string pattern, CancellationToken cancellationToken = default) =>
+ directory.GetFilesAsync(pattern, exclude: null, cancellationToken);
+
+ ///
+ /// Asynchronously returns an async-enumerable collection of files within the specified directory
+ /// that match the given glob pattern.
+ ///
+ /// The representing the directory to search.
+ /// The glob pattern to match against the names of files.
+ /// An optional glob pattern to exclude certain files.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ public static IAsyncEnumerable GetFilesAsync(this VirtualDirectory directory, string pattern, string? exclude, CancellationToken cancellationToken = default)
+ {
+ ArgumentNullException.ThrowIfNull(pattern);
+
+ return directory.GetFilesAsync([pattern], exclude is not null ? [exclude] : null, cancellationToken);
+ }
+
+ ///
+ /// Asynchronously returns an async-enumerable collection of files within the specified directory
+ /// that match any of the given glob patterns.
+ ///
+ /// The representing the directory to search.
+ /// An array of glob patterns to match against the names of files.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ public static IAsyncEnumerable GetFilesAsync(this VirtualDirectory directory, string[] patterns, CancellationToken cancellationToken = default) =>
+ directory.GetFilesAsync(patterns, excludes: null, cancellationToken);
+
+ ///
+ /// Asynchronously returns an async-enumerable collection of directories within the specified directory
+ /// that match the given glob pattern.
+ ///
+ /// The representing the directory to search.
+ /// The glob pattern to match against the names of directories.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ public static IAsyncEnumerable GetDirectoriesAsync(this VirtualDirectory directory, string pattern, CancellationToken cancellationToken = default) =>
+ directory.GetDirectoriesAsync(pattern, exclude: null, cancellationToken);
+
+ ///
+ /// Asynchronously returns an async-enumerable collection of directories within the specified directory
+ /// that match the given glob pattern.
+ ///
+ /// The representing the directory to search.
+ /// The glob pattern to match against the names of directories.
+ /// An optional glob pattern to exclude certain directories.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ public static IAsyncEnumerable GetDirectoriesAsync(this VirtualDirectory directory, string pattern, string? exclude, CancellationToken cancellationToken = default)
+ {
+ ArgumentNullException.ThrowIfNull(pattern);
+
+ return directory.GetDirectoriesAsync([pattern], exclude is not null ? [exclude] : null, cancellationToken);
+ }
+
+ ///
+ /// Asynchronously returns an async-enumerable collection of directories within the specified directory
+ /// that match any of the given glob patterns.
+ ///
+ /// The representing the directory to search.
+ /// An array of glob patterns to match against the names of directories.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ public static IAsyncEnumerable GetDirectoriesAsync(this VirtualDirectory directory, string[] patterns, CancellationToken cancellationToken = default) =>
+ directory.GetDirectoriesAsync(patterns, excludes: null, cancellationToken);
+}
diff --git a/src/Ramstack.FileSystem.Abstractions/VirtualFile.cs b/src/Ramstack.FileSystem.Abstractions/VirtualFile.cs
index 5db7414..97bde75 100644
--- a/src/Ramstack.FileSystem.Abstractions/VirtualFile.cs
+++ b/src/Ramstack.FileSystem.Abstractions/VirtualFile.cs
@@ -1,267 +1,267 @@
-namespace Ramstack.FileSystem;
-
-///
-/// Represents a virtual file within a specified file system.
-///
-[DebuggerDisplay("{FullName,nq}")]
-public abstract class VirtualFile : VirtualNode
-{
- ///
- /// Gets the full path of the directory containing this file.
- ///
- public string DirectoryName => VirtualPath.GetDirectoryName(FullName);
-
- ///
- /// Gets a instance representing the parent directory of this file.
- ///
- public VirtualDirectory Directory => FileSystem.GetDirectory(DirectoryName);
-
- ///
- /// Initializes a new instance of the class with the specified path.
- ///
- /// The full path of the file.
- protected VirtualFile(string path) : base(path)
- {
- }
-
- ///
- /// Initializes a new instance of the class with the specified path and properties.
- ///
- /// The full path of the file.
- /// The properties of the file.
- protected VirtualFile(string path, VirtualNodeProperties? properties) : base(path, properties)
- {
- }
-
- ///
- /// Asynchronously opens the file for reading.
- ///
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation,
- /// with a result of type for reading the file content.
- ///
- public ValueTask OpenReadAsync(CancellationToken cancellationToken = default) =>
- OpenReadCoreAsync(cancellationToken);
-
- ///
- /// Asynchronously opens the file for writing.
- ///
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation,
- /// with a result of type for writing to the file.
- ///
- public ValueTask OpenWriteAsync(CancellationToken cancellationToken = default)
- {
- EnsureWritable();
- Refresh();
-
- return OpenWriteCoreAsync(cancellationToken);
- }
-
- ///
- /// Asynchronously writes the specified content to the file, creating a new file or overwriting an existing one.
- ///
- /// A containing the content to write to the file.
- /// to overwrite an existing file; to throw an exception if the file already exists.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- ///
- ///
- /// If the file does not exist, it will be created.
- /// If the file exists and is , the existing file will be overwritten.
- /// If is and the file exists, an exception will be thrown.
- ///
- ///
- public ValueTask WriteAsync(Stream stream, bool overwrite, CancellationToken cancellationToken = default)
- {
- EnsureWritable();
- Refresh();
-
- return WriteCoreAsync(stream, overwrite, cancellationToken);
- }
-
- ///
- /// Asynchronously deletes the current file.
- ///
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- public ValueTask DeleteAsync(CancellationToken cancellationToken = default)
- {
- EnsureWritable();
- Refresh();
-
- return DeleteCoreAsync(cancellationToken);
- }
-
- ///
- /// Asynchronously copies the file to the specified destination path.
- ///
- /// The path of the destination file. This cannot be a directory.
- /// to overwrite an existing file; to throw an exception if the file already exists.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- ///
- ///
- /// If the file does not exist, it will be created.
- /// If the file exists and is , the existing file will be overwritten.
- /// If is and the file exists, an exception will be thrown.
- ///
- ///
- public ValueTask CopyToAsync(string destinationPath, bool overwrite, CancellationToken cancellationToken = default)
- {
- EnsureWritable();
-
- destinationPath = VirtualPath.Normalize(destinationPath);
- EnsureDistinctTargets(FullName, destinationPath);
-
- return CopyToCoreAsync(destinationPath, overwrite, cancellationToken);
- }
-
- ///
- /// Asynchronously copies the contents of the current to the specified destination .
- ///
- /// The destination where the contents will be copied to.
- /// to overwrite an existing file; to throw an exception if the file already exists.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A that represents the asynchronous copy operation.
- ///
- ///
- ///
- /// If the file does not exist, it will be created.
- /// If the file exists and is , the existing file will be overwritten.
- /// If is and the file exists, an exception will be thrown.
- ///
- ///
- public ValueTask CopyToAsync(VirtualFile destination, bool overwrite, CancellationToken cancellationToken = default)
- {
- destination.EnsureWritable();
- destination.Refresh();
-
- if (destination.FileSystem != FileSystem)
- return CopyToCoreAsync(destination, overwrite, cancellationToken);
-
- EnsureDistinctTargets(FullName, destination.FullName);
- return CopyToCoreAsync(destination.FullName, overwrite, cancellationToken);
- }
-
- ///
- /// Core implementation for asynchronously opening the file for reading.
- ///
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation,
- /// with a result of type for reading the file content.
- ///
- protected abstract ValueTask OpenReadCoreAsync(CancellationToken cancellationToken);
-
- ///
- /// Core implementation for asynchronously opening the file for writing.
- ///
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation,
- /// with a result of type for writing to the file.
- ///
- protected abstract ValueTask OpenWriteCoreAsync(CancellationToken cancellationToken);
-
- ///
- /// Core implementation for asynchronously writing the specified content to the file,
- /// creating a new file or overwriting an existing one.
- ///
- /// A containing the content to write to the file.
- /// to overwrite an existing file; to throw an exception if the file already exists.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- ///
- ///
- /// If the file does not exist, it must be created.
- /// If the file exists and is , the existing file must be overwritten.
- /// If is and the file exists, an exception must be thrown.
- ///
- ///
- protected abstract ValueTask WriteCoreAsync(Stream stream, bool overwrite, CancellationToken cancellationToken);
-
- ///
- /// Core implementation for asynchronously deleting the current file.
- ///
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- protected abstract ValueTask DeleteCoreAsync(CancellationToken cancellationToken);
-
- ///
- /// Core implementation for asynchronously copying the file to the specified destination path.
- ///
- /// The path of the destination file.
- /// to overwrite an existing file; to throw an exception if the file already exists.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- ///
- ///
- /// If the file does not exist, it must be created.
- /// If the file exists and is , the existing file must be overwritten.
- /// If is and the file exists, an exception must be thrown.
- ///
- ///
- protected virtual async ValueTask CopyToCoreAsync(string destinationPath, bool overwrite, CancellationToken cancellationToken)
- {
- await using var source = await OpenReadAsync(cancellationToken).ConfigureAwait(false);
- await FileSystem.WriteAsync(destinationPath, source, overwrite, cancellationToken).ConfigureAwait(false);
- }
-
- ///
- /// Core implementation for asynchronously copying the contents of the current to the specified destination .
- ///
- /// The destination where the contents will be copied to.
- /// to overwrite an existing file; to throw an exception if the file already exists.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A that represents the asynchronous copy operation.
- ///
- ///
- ///
- /// If the file does not exist, it must be created.
- /// If the file exists and is , the existing file must be overwritten.
- /// If is and the file exists, an exception must be thrown.
- ///
- ///
- protected virtual async ValueTask CopyToCoreAsync(VirtualFile destination, bool overwrite, CancellationToken cancellationToken)
- {
- await using var stream = await OpenReadAsync(cancellationToken).ConfigureAwait(false);
- await destination.WriteAsync(stream, overwrite, cancellationToken).ConfigureAwait(false);
- }
-
- ///
- /// Ensures that the source and destination paths are distinct.
- ///
- /// The source file path.
- /// The destination file path.
- ///
- /// Distinct target validation, if the underlying file system is case-insensitive,
- /// should be handled by the appropriate provider.
- ///
- private static void EnsureDistinctTargets(string path, string destinationPath)
- {
- // Distinct target validation, if the underlying file system is case-insensitive,
- // should be handled by the appropriate provider.
- if (path == destinationPath)
- Error(path);
-
- static void Error(string path) =>
- throw new IOException($"Cannot copy a file '{path}' to itself.");
- }
-}
+namespace Ramstack.FileSystem;
+
+///
+/// Represents a virtual file within a specified file system.
+///
+[DebuggerDisplay("{FullName,nq}")]
+public abstract class VirtualFile : VirtualNode
+{
+ ///
+ /// Gets the full path of the directory containing this file.
+ ///
+ public string DirectoryName => VirtualPath.GetDirectoryName(FullName);
+
+ ///
+ /// Gets a instance representing the parent directory of this file.
+ ///
+ public VirtualDirectory Directory => FileSystem.GetDirectory(DirectoryName);
+
+ ///
+ /// Initializes a new instance of the class with the specified path.
+ ///
+ /// The full path of the file.
+ protected VirtualFile(string path) : base(path)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with the specified path and properties.
+ ///
+ /// The full path of the file.
+ /// The properties of the file.
+ protected VirtualFile(string path, VirtualNodeProperties? properties) : base(path, properties)
+ {
+ }
+
+ ///
+ /// Asynchronously opens the file for reading.
+ ///
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation,
+ /// with a result of type for reading the file content.
+ ///
+ public ValueTask OpenReadAsync(CancellationToken cancellationToken = default) =>
+ OpenReadCoreAsync(cancellationToken);
+
+ ///
+ /// Asynchronously opens the file for writing.
+ ///
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation,
+ /// with a result of type for writing to the file.
+ ///
+ public ValueTask OpenWriteAsync(CancellationToken cancellationToken = default)
+ {
+ EnsureWritable();
+ Refresh();
+
+ return OpenWriteCoreAsync(cancellationToken);
+ }
+
+ ///
+ /// Asynchronously writes the specified content to the file, creating a new file or overwriting an existing one.
+ ///
+ /// A containing the content to write to the file.
+ /// to overwrite an existing file; to throw an exception if the file already exists.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ ///
+ ///
+ /// If the file does not exist, it will be created.
+ /// If the file exists and is , the existing file will be overwritten.
+ /// If is and the file exists, an exception will be thrown.
+ ///
+ ///
+ public ValueTask WriteAsync(Stream stream, bool overwrite, CancellationToken cancellationToken = default)
+ {
+ EnsureWritable();
+ Refresh();
+
+ return WriteCoreAsync(stream, overwrite, cancellationToken);
+ }
+
+ ///
+ /// Asynchronously deletes the current file.
+ ///
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ public ValueTask DeleteAsync(CancellationToken cancellationToken = default)
+ {
+ EnsureWritable();
+ Refresh();
+
+ return DeleteCoreAsync(cancellationToken);
+ }
+
+ ///
+ /// Asynchronously copies the file to the specified destination path.
+ ///
+ /// The path of the destination file. This cannot be a directory.
+ /// to overwrite an existing file; to throw an exception if the file already exists.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ ///
+ ///
+ /// If the file does not exist, it will be created.
+ /// If the file exists and is , the existing file will be overwritten.
+ /// If is and the file exists, an exception will be thrown.
+ ///
+ ///
+ public ValueTask CopyToAsync(string destinationPath, bool overwrite, CancellationToken cancellationToken = default)
+ {
+ EnsureWritable();
+
+ destinationPath = VirtualPath.Normalize(destinationPath);
+ EnsureDistinctTargets(FullName, destinationPath);
+
+ return CopyToCoreAsync(destinationPath, overwrite, cancellationToken);
+ }
+
+ ///
+ /// Asynchronously copies the contents of the current to the specified destination .
+ ///
+ /// The destination where the contents will be copied to.
+ /// to overwrite an existing file; to throw an exception if the file already exists.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A that represents the asynchronous copy operation.
+ ///
+ ///
+ ///
+ /// If the file does not exist, it will be created.
+ /// If the file exists and is , the existing file will be overwritten.
+ /// If is and the file exists, an exception will be thrown.
+ ///
+ ///
+ public ValueTask CopyToAsync(VirtualFile destination, bool overwrite, CancellationToken cancellationToken = default)
+ {
+ destination.EnsureWritable();
+ destination.Refresh();
+
+ if (destination.FileSystem != FileSystem)
+ return CopyToCoreAsync(destination, overwrite, cancellationToken);
+
+ EnsureDistinctTargets(FullName, destination.FullName);
+ return CopyToCoreAsync(destination.FullName, overwrite, cancellationToken);
+ }
+
+ ///
+ /// Core implementation for asynchronously opening the file for reading.
+ ///
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation,
+ /// with a result of type for reading the file content.
+ ///
+ protected abstract ValueTask OpenReadCoreAsync(CancellationToken cancellationToken);
+
+ ///
+ /// Core implementation for asynchronously opening the file for writing.
+ ///
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation,
+ /// with a result of type for writing to the file.
+ ///
+ protected abstract ValueTask OpenWriteCoreAsync(CancellationToken cancellationToken);
+
+ ///
+ /// Core implementation for asynchronously writing the specified content to the file,
+ /// creating a new file or overwriting an existing one.
+ ///
+ /// A containing the content to write to the file.
+ /// to overwrite an existing file; to throw an exception if the file already exists.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ ///
+ ///
+ /// If the file does not exist, it must be created.
+ /// If the file exists and is , the existing file must be overwritten.
+ /// If is and the file exists, an exception must be thrown.
+ ///
+ ///
+ protected abstract ValueTask WriteCoreAsync(Stream stream, bool overwrite, CancellationToken cancellationToken);
+
+ ///
+ /// Core implementation for asynchronously deleting the current file.
+ ///
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ protected abstract ValueTask DeleteCoreAsync(CancellationToken cancellationToken);
+
+ ///
+ /// Core implementation for asynchronously copying the file to the specified destination path.
+ ///
+ /// The path of the destination file.
+ /// to overwrite an existing file; to throw an exception if the file already exists.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ ///
+ ///
+ /// If the file does not exist, it must be created.
+ /// If the file exists and is , the existing file must be overwritten.
+ /// If is and the file exists, an exception must be thrown.
+ ///
+ ///
+ protected virtual async ValueTask CopyToCoreAsync(string destinationPath, bool overwrite, CancellationToken cancellationToken)
+ {
+ await using var source = await OpenReadAsync(cancellationToken).ConfigureAwait(false);
+ await FileSystem.WriteAsync(destinationPath, source, overwrite, cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ /// Core implementation for asynchronously copying the contents of the current to the specified destination .
+ ///
+ /// The destination where the contents will be copied to.
+ /// to overwrite an existing file; to throw an exception if the file already exists.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A that represents the asynchronous copy operation.
+ ///
+ ///
+ ///
+ /// If the file does not exist, it must be created.
+ /// If the file exists and is , the existing file must be overwritten.
+ /// If is and the file exists, an exception must be thrown.
+ ///
+ ///
+ protected virtual async ValueTask CopyToCoreAsync(VirtualFile destination, bool overwrite, CancellationToken cancellationToken)
+ {
+ await using var stream = await OpenReadAsync(cancellationToken).ConfigureAwait(false);
+ await destination.WriteAsync(stream, overwrite, cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ /// Ensures that the source and destination paths are distinct.
+ ///
+ /// The source file path.
+ /// The destination file path.
+ ///
+ /// Distinct target validation, if the underlying file system is case-insensitive,
+ /// should be handled by the appropriate provider.
+ ///
+ private static void EnsureDistinctTargets(string path, string destinationPath)
+ {
+ // Distinct target validation, if the underlying file system is case-insensitive,
+ // should be handled by the appropriate provider.
+ if (path == destinationPath)
+ Error(path);
+
+ static void Error(string path) =>
+ throw new IOException($"Cannot copy a file '{path}' to itself.");
+ }
+}
diff --git a/src/Ramstack.FileSystem.Abstractions/VirtualFileExtensions.cs b/src/Ramstack.FileSystem.Abstractions/VirtualFileExtensions.cs
index f82d159..425eb32 100644
--- a/src/Ramstack.FileSystem.Abstractions/VirtualFileExtensions.cs
+++ b/src/Ramstack.FileSystem.Abstractions/VirtualFileExtensions.cs
@@ -1,357 +1,357 @@
-using System.Buffers;
-using System.Text;
-
-namespace Ramstack.FileSystem;
-
-///
-/// Provides extension methods for the class.
-///
-public static class VirtualFileExtensions
-{
- ///
- /// Asynchronously returns a with
- /// character encoding that reads from the specified text file.
- ///
- /// The file to get the for.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- /// The result is a that reads from the text file.
- ///
- public static ValueTask OpenTextAsync(this VirtualFile file, CancellationToken cancellationToken = default) =>
- file.OpenTextAsync(encoding: null, cancellationToken);
-
- ///
- /// Asynchronously returns a with the specified character encoding
- /// that reads from the specified text file.
- ///
- /// The file to get the for.
- /// The encoding applied to the contents.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- /// The result is a that reads from the text file.
- ///
- public static async ValueTask OpenTextAsync(this VirtualFile file, Encoding? encoding, CancellationToken cancellationToken = default)
- {
- var stream = await file.OpenReadAsync(cancellationToken).ConfigureAwait(false);
- return new StreamReader(stream, encoding!);
- }
-
- ///
- /// Asynchronously writes the specified content to a file. If the file exists, an exception will be thrown.
- ///
- /// The file to write to.
- /// A containing the content to write to the file.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- public static ValueTask WriteAsync(this VirtualFile file, Stream stream, CancellationToken cancellationToken = default) =>
- file.WriteAsync(stream, overwrite: false, cancellationToken);
-
- ///
- /// Asynchronously reads all the text in the current file.
- ///
- /// The file from which to read the entire text content.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation,
- /// containing the full text from the current file.
- ///
- public static ValueTask ReadAllTextAsync(this VirtualFile file, CancellationToken cancellationToken = default) =>
- ReadAllTextAsync(file, encoding: null, cancellationToken);
-
- ///
- /// Asynchronously reads all the text in the current file with the specified encoding.
- ///
- /// The file from which to read the entire text content.
- /// The encoding applied to the contents.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation,
- /// containing the full text from the current file.
- ///
- public static async ValueTask ReadAllTextAsync(this VirtualFile file, Encoding? encoding, CancellationToken cancellationToken = default)
- {
- // ReSharper disable once UseAwaitUsing
- using var stream = await file.OpenReadAsync(cancellationToken).ConfigureAwait(false);
- using var reader = new StreamReader(stream, encoding!);
-
- return await reader.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
- }
-
- ///
- /// Asynchronously reads all lines of the current file.
- ///
- /// The file to read from.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation,
- /// containing an array of all lines in the current file.
- ///
- public static ValueTask ReadAllLinesAsync(this VirtualFile file, CancellationToken cancellationToken = default) =>
- ReadAllLinesAsync(file, encoding: null, cancellationToken);
-
- ///
- /// Asynchronously reads all lines of the current file with the specified encoding.
- ///
- /// The file to read from.
- /// The encoding applied to the contents.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation,
- /// containing an array of all lines in the current file.
- ///
- public static async ValueTask ReadAllLinesAsync(this VirtualFile file, Encoding? encoding, CancellationToken cancellationToken = default)
- {
- var stream = await file.OpenReadAsync(cancellationToken).ConfigureAwait(false);
- using var reader = new StreamReader(stream, encoding!);
-
- var list = new List();
- while (await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false) is { } line)
- list.Add(line);
-
- return list.ToArray();
- }
-
- ///
- /// Asynchronously reads the entire contents of the current file into a byte array.
- ///
- /// The file to read from.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation,
- /// containing an array of the file's bytes.
- ///
- public static async ValueTask ReadAllBytesAsync(this VirtualFile file, CancellationToken cancellationToken = default)
- {
- // ReSharper disable once UseAwaitUsing
- using var stream = await file.OpenReadAsync(cancellationToken).ConfigureAwait(false);
-
- var length = stream.CanSeek ? stream.Length : 0;
- if (length > Array.MaxLength)
- throw new IOException("The file is too large.");
-
- // https://github.com/dotnet/runtime/blob/5535e31a712343a63f5d7d796cd874e563e5ac14/src/libraries/System.Private.CoreLib/src/System/IO/File.cs#L660
- // Some file systems (e.g. procfs on Linux) return 0 for length even when there's content.
- // Thus we need to assume 0 doesn't mean empty.
- var task = length <= 0
- ? ReadAllBytesUnknownLengthImplAsync(stream, cancellationToken)
- : ReadAllBytesImplAsync(stream, cancellationToken);
-
- return await task.ConfigureAwait(false);
-
- static async ValueTask ReadAllBytesImplAsync(Stream stream, CancellationToken cancellationToken)
- {
- var bytes = new byte[stream.Length];
- var total = 0;
-
- do
- {
- var count = await stream
- .ReadAsync(bytes.AsMemory(total), cancellationToken)
- .ConfigureAwait(false);
-
- if (count == 0)
- Error_EndOfStream();
-
- total += count;
- } while (total < bytes.Length);
-
- return bytes;
- }
-
- static async ValueTask ReadAllBytesUnknownLengthImplAsync(Stream stream, CancellationToken cancellationToken)
- {
- var bytes = ArrayPool.Shared.Rent(512);
- var total = 0;
-
- while (true)
- {
- if (total == bytes.Length)
- bytes = ResizeBuffer(bytes);
-
- var count = await stream
- .ReadAsync(bytes.AsMemory(total), cancellationToken)
- .ConfigureAwait(false);
-
- if (count == 0)
- break;
-
- total += count;
- }
-
- var result = bytes.AsSpan(0, total).ToArray();
- ArrayPool.Shared.Return(bytes);
- return result;
-
- static byte[] ResizeBuffer(byte[] oldArray)
- {
- var length = oldArray.Length * 2;
- if ((uint)length > (uint)Array.MaxLength)
- length = Math.Max(Array.MaxLength, oldArray.Length + 1);
-
- var newArray = ArrayPool.Shared.Rent(length);
- oldArray.AsSpan().TryCopyTo(newArray);
-
- ArrayPool.Shared.Return(oldArray);
- return newArray;
- }
- }
-
- static void Error_EndOfStream() =>
- throw new EndOfStreamException();
- }
-
- ///
- /// Asynchronously writes the specified string to the current file. If the file already exists, it is truncated and overwritten.
- ///
- /// The file to write to.
- /// The contents to write to the file.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- public static ValueTask WriteAllTextAsync(this VirtualFile file, string contents, CancellationToken cancellationToken = default) =>
- WriteAllTextAsync(file, contents.AsMemory(), encoding: null, cancellationToken);
-
- ///
- /// Asynchronously writes the specified string to the current file. If the file already exists, it is truncated and overwritten.
- ///
- /// The file to write to.
- /// The contents to write to the file.
- /// The encoding to apply to the string.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- public static ValueTask WriteAllTextAsync(this VirtualFile file, string contents, Encoding? encoding, CancellationToken cancellationToken = default) =>
- WriteAllTextAsync(file, contents.AsMemory(), encoding, cancellationToken);
-
- ///
- /// Asynchronously writes the specified string to the current file. If the file already exists, it is truncated and overwritten.
- ///
- /// The file to write to.
- /// The contents to write to the file.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- public static ValueTask WriteAllTextAsync(this VirtualFile file, ReadOnlyMemory contents, CancellationToken cancellationToken = default) =>
- WriteAllTextAsync(file, contents, encoding: null, cancellationToken);
-
- ///
- /// Asynchronously writes the specified string to the current file. If the file already exists, it is truncated and overwritten.
- ///
- /// The file to write to.
- /// The contents to write to the file.
- /// The encoding to apply to the string.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- public static async ValueTask WriteAllTextAsync(this VirtualFile file, ReadOnlyMemory contents, Encoding? encoding, CancellationToken cancellationToken = default)
- {
- await using var stream = await file.OpenWriteAsync(cancellationToken).ConfigureAwait(false);
- await using var writer = new StreamWriter(stream, encoding!);
- await writer.WriteAsync(contents, cancellationToken).ConfigureAwait(false);
- }
-
- ///
- /// Asynchronously writes the specified lines to the current file. If the file already exists, it is truncated and overwritten.
- ///
- /// The file to write to.
- /// The contents to write to the file.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- public static ValueTask WriteAllLinesAsync(this VirtualFile file, IEnumerable contents, CancellationToken cancellationToken = default) =>
- WriteAllLinesAsync(file, contents, encoding: null, cancellationToken);
-
- ///
- /// Asynchronously writes the specified lines to the current file. If the file already exists, it is truncated and overwritten.
- ///
- /// The file to write to.
- /// The contents to write to the file.
- /// The encoding to apply to the string.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- public static async ValueTask WriteAllLinesAsync(this VirtualFile file, IEnumerable contents, Encoding? encoding, CancellationToken cancellationToken = default)
- {
- await using var stream = await file.OpenWriteAsync(cancellationToken).ConfigureAwait(false);
- await using var writer = new StreamWriter(stream, encoding, bufferSize: -1, leaveOpen: false);
-
- foreach (var line in contents)
- await writer.WriteLineAsync(line.AsMemory(), cancellationToken).ConfigureAwait(false);
- }
-
- ///
- /// Asynchronously writes the specified byte array to the current file. If the file already exists, it is truncated and overwritten.
- ///
- /// The file to write to.
- /// The bytes to write to the file.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- public static ValueTask WriteAllBytesAsync(this VirtualFile file, byte[] bytes, CancellationToken cancellationToken = default) =>
- WriteAllBytesAsync(file, bytes.AsMemory(), cancellationToken);
-
- ///
- /// Asynchronously writes the specified byte array to the current file. If the file already exists, it is truncated and overwritten.
- ///
- /// The file to write to.
- /// The bytes to write to the file.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- public static async ValueTask WriteAllBytesAsync(this VirtualFile file, ReadOnlyMemory bytes, CancellationToken cancellationToken = default)
- {
- await using var stream = await file.OpenWriteAsync(cancellationToken).ConfigureAwait(false);
- await stream.WriteAsync(bytes, cancellationToken).ConfigureAwait(false);
- }
-
- ///
- /// Asynchronously copies the file to the specified destination path.
- ///
- /// The source to copy from.
- /// The path of the destination file. This cannot be a directory.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- public static ValueTask CopyToAsync(this VirtualFile file, string destinationPath, CancellationToken cancellationToken = default) =>
- file.CopyToAsync(destinationPath, overwrite: false, cancellationToken);
-
- ///
- /// Asynchronously copies the contents of the current to the specified destination .
- ///
- /// The source to copy from.
- /// The destination where the contents will be copied to.
- /// An optional cancellation token to cancel the operation. Defaults to .
- ///
- /// A that represents the asynchronous copy operation.
- ///
- public static ValueTask CopyToAsync(this VirtualFile file, VirtualFile destination, CancellationToken cancellationToken = default) =>
- file.CopyToAsync(destination, overwrite: false, cancellationToken);
-
- ///
- /// Asynchronously returns the size of the specified file in bytes.
- ///
- /// The file to get the size of.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- /// The result is the size of the specified file in bytes, or -1 if the file does not exist.
- ///
- public static async ValueTask GetLengthAsync(this VirtualFile file, CancellationToken cancellationToken = default)
- {
- var properties = await file.GetPropertiesAsync(cancellationToken).ConfigureAwait(false);
- return properties.Length;
- }
-}
+using System.Buffers;
+using System.Text;
+
+namespace Ramstack.FileSystem;
+
+///
+/// Provides extension methods for the class.
+///
+public static class VirtualFileExtensions
+{
+ ///
+ /// Asynchronously returns a with
+ /// character encoding that reads from the specified text file.
+ ///
+ /// The file to get the for.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ /// The result is a that reads from the text file.
+ ///
+ public static ValueTask OpenTextAsync(this VirtualFile file, CancellationToken cancellationToken = default) =>
+ file.OpenTextAsync(encoding: null, cancellationToken);
+
+ ///
+ /// Asynchronously returns a with the specified character encoding
+ /// that reads from the specified text file.
+ ///
+ /// The file to get the for.
+ /// The encoding applied to the contents.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ /// The result is a that reads from the text file.
+ ///
+ public static async ValueTask OpenTextAsync(this VirtualFile file, Encoding? encoding, CancellationToken cancellationToken = default)
+ {
+ var stream = await file.OpenReadAsync(cancellationToken).ConfigureAwait(false);
+ return new StreamReader(stream, encoding!);
+ }
+
+ ///
+ /// Asynchronously writes the specified content to a file. If the file exists, an exception will be thrown.
+ ///
+ /// The file to write to.
+ /// A containing the content to write to the file.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ public static ValueTask WriteAsync(this VirtualFile file, Stream stream, CancellationToken cancellationToken = default) =>
+ file.WriteAsync(stream, overwrite: false, cancellationToken);
+
+ ///
+ /// Asynchronously reads all the text in the current file.
+ ///
+ /// The file from which to read the entire text content.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation,
+ /// containing the full text from the current file.
+ ///
+ public static ValueTask ReadAllTextAsync(this VirtualFile file, CancellationToken cancellationToken = default) =>
+ ReadAllTextAsync(file, encoding: null, cancellationToken);
+
+ ///
+ /// Asynchronously reads all the text in the current file with the specified encoding.
+ ///
+ /// The file from which to read the entire text content.
+ /// The encoding applied to the contents.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation,
+ /// containing the full text from the current file.
+ ///
+ public static async ValueTask ReadAllTextAsync(this VirtualFile file, Encoding? encoding, CancellationToken cancellationToken = default)
+ {
+ // ReSharper disable once UseAwaitUsing
+ using var stream = await file.OpenReadAsync(cancellationToken).ConfigureAwait(false);
+ using var reader = new StreamReader(stream, encoding!);
+
+ return await reader.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ /// Asynchronously reads all lines of the current file.
+ ///
+ /// The file to read from.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation,
+ /// containing an array of all lines in the current file.
+ ///
+ public static ValueTask ReadAllLinesAsync(this VirtualFile file, CancellationToken cancellationToken = default) =>
+ ReadAllLinesAsync(file, encoding: null, cancellationToken);
+
+ ///
+ /// Asynchronously reads all lines of the current file with the specified encoding.
+ ///
+ /// The file to read from.
+ /// The encoding applied to the contents.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation,
+ /// containing an array of all lines in the current file.
+ ///
+ public static async ValueTask ReadAllLinesAsync(this VirtualFile file, Encoding? encoding, CancellationToken cancellationToken = default)
+ {
+ var stream = await file.OpenReadAsync(cancellationToken).ConfigureAwait(false);
+ using var reader = new StreamReader(stream, encoding!);
+
+ var list = new List();
+ while (await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false) is { } line)
+ list.Add(line);
+
+ return list.ToArray();
+ }
+
+ ///
+ /// Asynchronously reads the entire contents of the current file into a byte array.
+ ///
+ /// The file to read from.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation,
+ /// containing an array of the file's bytes.
+ ///
+ public static async ValueTask ReadAllBytesAsync(this VirtualFile file, CancellationToken cancellationToken = default)
+ {
+ // ReSharper disable once UseAwaitUsing
+ using var stream = await file.OpenReadAsync(cancellationToken).ConfigureAwait(false);
+
+ var length = stream.CanSeek ? stream.Length : 0;
+ if (length > Array.MaxLength)
+ throw new IOException("The file is too large.");
+
+ // https://github.com/dotnet/runtime/blob/5535e31a712343a63f5d7d796cd874e563e5ac14/src/libraries/System.Private.CoreLib/src/System/IO/File.cs#L660
+ // Some file systems (e.g. procfs on Linux) return 0 for length even when there's content.
+ // Thus we need to assume 0 doesn't mean empty.
+ var task = length <= 0
+ ? ReadAllBytesUnknownLengthImplAsync(stream, cancellationToken)
+ : ReadAllBytesImplAsync(stream, cancellationToken);
+
+ return await task.ConfigureAwait(false);
+
+ static async ValueTask ReadAllBytesImplAsync(Stream stream, CancellationToken cancellationToken)
+ {
+ var bytes = new byte[stream.Length];
+ var total = 0;
+
+ do
+ {
+ var count = await stream
+ .ReadAsync(bytes.AsMemory(total), cancellationToken)
+ .ConfigureAwait(false);
+
+ if (count == 0)
+ Error_EndOfStream();
+
+ total += count;
+ } while (total < bytes.Length);
+
+ return bytes;
+ }
+
+ static async ValueTask ReadAllBytesUnknownLengthImplAsync(Stream stream, CancellationToken cancellationToken)
+ {
+ var bytes = ArrayPool.Shared.Rent(512);
+ var total = 0;
+
+ while (true)
+ {
+ if (total == bytes.Length)
+ bytes = ResizeBuffer(bytes);
+
+ var count = await stream
+ .ReadAsync(bytes.AsMemory(total), cancellationToken)
+ .ConfigureAwait(false);
+
+ if (count == 0)
+ break;
+
+ total += count;
+ }
+
+ var result = bytes.AsSpan(0, total).ToArray();
+ ArrayPool.Shared.Return(bytes);
+ return result;
+
+ static byte[] ResizeBuffer(byte[] oldArray)
+ {
+ var length = oldArray.Length * 2;
+ if ((uint)length > (uint)Array.MaxLength)
+ length = Math.Max(Array.MaxLength, oldArray.Length + 1);
+
+ var newArray = ArrayPool.Shared.Rent(length);
+ oldArray.AsSpan().TryCopyTo(newArray);
+
+ ArrayPool.Shared.Return(oldArray);
+ return newArray;
+ }
+ }
+
+ static void Error_EndOfStream() =>
+ throw new EndOfStreamException();
+ }
+
+ ///
+ /// Asynchronously writes the specified string to the current file. If the file already exists, it is truncated and overwritten.
+ ///
+ /// The file to write to.
+ /// The contents to write to the file.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ public static ValueTask WriteAllTextAsync(this VirtualFile file, string contents, CancellationToken cancellationToken = default) =>
+ WriteAllTextAsync(file, contents.AsMemory(), encoding: null, cancellationToken);
+
+ ///
+ /// Asynchronously writes the specified string to the current file. If the file already exists, it is truncated and overwritten.
+ ///
+ /// The file to write to.
+ /// The contents to write to the file.
+ /// The encoding to apply to the string.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ public static ValueTask WriteAllTextAsync(this VirtualFile file, string contents, Encoding? encoding, CancellationToken cancellationToken = default) =>
+ WriteAllTextAsync(file, contents.AsMemory(), encoding, cancellationToken);
+
+ ///
+ /// Asynchronously writes the specified string to the current file. If the file already exists, it is truncated and overwritten.
+ ///
+ /// The file to write to.
+ /// The contents to write to the file.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ public static ValueTask WriteAllTextAsync(this VirtualFile file, ReadOnlyMemory contents, CancellationToken cancellationToken = default) =>
+ WriteAllTextAsync(file, contents, encoding: null, cancellationToken);
+
+ ///
+ /// Asynchronously writes the specified string to the current file. If the file already exists, it is truncated and overwritten.
+ ///
+ /// The file to write to.
+ /// The contents to write to the file.
+ /// The encoding to apply to the string.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ public static async ValueTask WriteAllTextAsync(this VirtualFile file, ReadOnlyMemory contents, Encoding? encoding, CancellationToken cancellationToken = default)
+ {
+ await using var stream = await file.OpenWriteAsync(cancellationToken).ConfigureAwait(false);
+ await using var writer = new StreamWriter(stream, encoding!);
+ await writer.WriteAsync(contents, cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ /// Asynchronously writes the specified lines to the current file. If the file already exists, it is truncated and overwritten.
+ ///
+ /// The file to write to.
+ /// The contents to write to the file.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ public static ValueTask WriteAllLinesAsync(this VirtualFile file, IEnumerable contents, CancellationToken cancellationToken = default) =>
+ WriteAllLinesAsync(file, contents, encoding: null, cancellationToken);
+
+ ///
+ /// Asynchronously writes the specified lines to the current file. If the file already exists, it is truncated and overwritten.
+ ///
+ /// The file to write to.
+ /// The contents to write to the file.
+ /// The encoding to apply to the string.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ public static async ValueTask WriteAllLinesAsync(this VirtualFile file, IEnumerable contents, Encoding? encoding, CancellationToken cancellationToken = default)
+ {
+ await using var stream = await file.OpenWriteAsync(cancellationToken).ConfigureAwait(false);
+ await using var writer = new StreamWriter(stream, encoding, bufferSize: -1, leaveOpen: false);
+
+ foreach (var line in contents)
+ await writer.WriteLineAsync(line.AsMemory(), cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ /// Asynchronously writes the specified byte array to the current file. If the file already exists, it is truncated and overwritten.
+ ///
+ /// The file to write to.
+ /// The bytes to write to the file.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ public static ValueTask WriteAllBytesAsync(this VirtualFile file, byte[] bytes, CancellationToken cancellationToken = default) =>
+ WriteAllBytesAsync(file, bytes.AsMemory(), cancellationToken);
+
+ ///
+ /// Asynchronously writes the specified byte array to the current file. If the file already exists, it is truncated and overwritten.
+ ///
+ /// The file to write to.
+ /// The bytes to write to the file.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ public static async ValueTask WriteAllBytesAsync(this VirtualFile file, ReadOnlyMemory bytes, CancellationToken cancellationToken = default)
+ {
+ await using var stream = await file.OpenWriteAsync(cancellationToken).ConfigureAwait(false);
+ await stream.WriteAsync(bytes, cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ /// Asynchronously copies the file to the specified destination path.
+ ///
+ /// The source to copy from.
+ /// The path of the destination file. This cannot be a directory.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ public static ValueTask CopyToAsync(this VirtualFile file, string destinationPath, CancellationToken cancellationToken = default) =>
+ file.CopyToAsync(destinationPath, overwrite: false, cancellationToken);
+
+ ///
+ /// Asynchronously copies the contents of the current to the specified destination .
+ ///
+ /// The source to copy from.
+ /// The destination where the contents will be copied to.
+ /// An optional cancellation token to cancel the operation. Defaults to .
+ ///
+ /// A that represents the asynchronous copy operation.
+ ///
+ public static ValueTask CopyToAsync(this VirtualFile file, VirtualFile destination, CancellationToken cancellationToken = default) =>
+ file.CopyToAsync(destination, overwrite: false, cancellationToken);
+
+ ///
+ /// Asynchronously returns the size of the specified file in bytes.
+ ///
+ /// The file to get the size of.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ /// The result is the size of the specified file in bytes, or -1 if the file does not exist.
+ ///
+ public static async ValueTask GetLengthAsync(this VirtualFile file, CancellationToken cancellationToken = default)
+ {
+ var properties = await file.GetPropertiesAsync(cancellationToken).ConfigureAwait(false);
+ return properties.Length;
+ }
+}
diff --git a/src/Ramstack.FileSystem.Abstractions/VirtualFileSystemExtensions.Enumeration.cs b/src/Ramstack.FileSystem.Abstractions/VirtualFileSystemExtensions.Enumeration.cs
index 6e226e1..7661802 100644
--- a/src/Ramstack.FileSystem.Abstractions/VirtualFileSystemExtensions.Enumeration.cs
+++ b/src/Ramstack.FileSystem.Abstractions/VirtualFileSystemExtensions.Enumeration.cs
@@ -1,169 +1,169 @@
-namespace Ramstack.FileSystem;
-
-partial class VirtualFileSystemExtensions
-{
- ///
- /// Asynchronously returns an async-enumerable collection of file nodes (both directories and files)
- /// in the specified path that match the given glob pattern.
- ///
- /// The file system to use.
- /// The path to the directory to search.
- /// The glob pattern to match against the names of file nodes.
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- public static IAsyncEnumerable GetFileNodesAsync(this IVirtualFileSystem fs, string path, string pattern, CancellationToken cancellationToken = default) =>
- fs.GetDirectory(path).GetFileNodesAsync(pattern, exclude: null, cancellationToken);
-
- ///
- /// Asynchronously returns an async-enumerable collection of file nodes (both directories and files)
- /// in the specified path that match the given glob pattern.
- ///
- /// The file system to use.
- /// The path to the directory to search.
- /// The glob pattern to match against the names of file nodes.
- /// An optional glob pattern to exclude certain file nodes.
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- public static IAsyncEnumerable GetFileNodesAsync(this IVirtualFileSystem fs, string path, string pattern, string? exclude, CancellationToken cancellationToken = default) =>
- fs.GetDirectory(path).GetFileNodesAsync([pattern], exclude is not null ? [exclude] : null, cancellationToken);
-
- ///
- /// Asynchronously returns an async-enumerable collection of file nodes (both directories and files)
- /// in the specified path that match any of the given glob patterns.
- ///
- /// The file system to use.
- /// The path to the directory to search.
- /// An array of glob patterns to match against the names of file nodes.
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- public static IAsyncEnumerable GetFileNodesAsync(this IVirtualFileSystem fs, string path, string[] patterns, CancellationToken cancellationToken = default) =>
- fs.GetDirectory(path).GetFileNodesAsync(patterns, excludes: null, cancellationToken);
-
- ///
- /// Asynchronously returns an async-enumerable collection of file nodes (both directories and files)
- /// in the specified path that match any of the given glob patterns.
- ///
- /// The file system to use.
- /// The path to the directory to search.
- /// An array of glob patterns to match against the names of file nodes.
- /// An optional array of glob patterns to exclude file nodes.
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- public static IAsyncEnumerable GetFileNodesAsync(this IVirtualFileSystem fs, string path, string[] patterns, string[]? excludes, CancellationToken cancellationToken = default) =>
- fs.GetDirectory(path).GetFileNodesAsync(patterns, excludes, cancellationToken);
-
- ///
- /// Asynchronously returns an async-enumerable collection of files in the specified path that match the given glob pattern.
- ///
- /// The file system to use.
- /// The path to the directory to search.
- /// The glob pattern to match against the names of files.
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- public static IAsyncEnumerable GetFilesAsync(this IVirtualFileSystem fs, string path, string pattern, CancellationToken cancellationToken = default) =>
- fs.GetDirectory(path).GetFilesAsync(pattern, exclude: null, cancellationToken);
-
- ///
- /// Asynchronously returns an async-enumerable collection of files in the specified path that match the given glob pattern.
- ///
- /// The file system to use.
- /// The path to the directory to search.
- /// The glob pattern to match against the names of files.
- /// An optional glob pattern to exclude certain files.
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- public static IAsyncEnumerable GetFilesAsync(this IVirtualFileSystem fs, string path, string pattern, string? exclude, CancellationToken cancellationToken = default) =>
- fs.GetDirectory(path).GetFilesAsync([pattern], exclude is not null ? [exclude] : null, cancellationToken);
-
- ///
- /// Asynchronously returns an async-enumerable collection of files in the specified path that match any of the given glob patterns.
- ///
- /// The file system to use.
- /// The path to the directory to search.
- /// An array of glob patterns to match against the names of files.
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- public static IAsyncEnumerable GetFilesAsync(this IVirtualFileSystem fs, string path, string[] patterns, CancellationToken cancellationToken = default) =>
- fs.GetDirectory(path).GetFilesAsync(patterns, excludes: null, cancellationToken);
-
- ///
- /// Asynchronously returns an async-enumerable collection of files in the specified path that match any of the given glob patterns.
- ///
- /// The file system to use.
- /// The path to the directory to search.
- /// An array of glob patterns to match against the names of files.
- /// An optional array of glob patterns to exclude files.
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- public static IAsyncEnumerable GetFilesAsync(this IVirtualFileSystem fs, string path, string[] patterns, string[]? excludes, CancellationToken cancellationToken = default) =>
- fs.GetDirectory(path).GetFilesAsync(patterns, excludes, cancellationToken);
-
- ///
- /// Asynchronously returns an async-enumerable collection of directories in the specified path that match the given glob pattern.
- ///
- /// The file system to use.
- /// The path to the directory to search.
- /// The glob pattern to match against the names of directories.
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- public static IAsyncEnumerable GetDirectoriesAsync(this IVirtualFileSystem fs, string path, string pattern, CancellationToken cancellationToken = default) =>
- fs.GetDirectory(path).GetDirectoriesAsync(pattern, exclude: null, cancellationToken);
-
- ///
- /// Asynchronously returns an async-enumerable collection of directories in the specified path that match the given glob pattern.
- ///
- /// The file system to use.
- /// The path to the directory to search.
- /// The glob pattern to match against the names of directories.
- /// An optional glob pattern to exclude certain directories.
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- public static IAsyncEnumerable GetDirectoriesAsync(this IVirtualFileSystem fs, string path, string pattern, string? exclude, CancellationToken cancellationToken = default) =>
- fs.GetDirectory(path).GetDirectoriesAsync([pattern], exclude is not null ? [exclude] : null, cancellationToken);
-
- ///
- /// Asynchronously returns an async-enumerable collection of directories in the specified path that match any of the given glob patterns.
- ///
- /// The file system to use.
- /// The path to the directory to search.
- /// An array of glob patterns to match against the names of directories.
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- public static IAsyncEnumerable GetDirectoriesAsync(this IVirtualFileSystem fs, string path, string[] patterns, CancellationToken cancellationToken = default) =>
- fs.GetDirectory(path).GetDirectoriesAsync(patterns, excludes: null, cancellationToken);
-
- ///
- /// Asynchronously returns an async-enumerable collection of directories in the specified path that match any of the given glob patterns.
- ///
- /// The file system to use.
- /// The path to the directory to search.
- /// An array of glob patterns to match against the names of directories.
- /// An optional array of glob patterns to exclude directories.
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- public static IAsyncEnumerable GetDirectoriesAsync(this IVirtualFileSystem fs, string path, string[] patterns, string[]? excludes, CancellationToken cancellationToken = default) =>
- fs.GetDirectory(path).GetDirectoriesAsync(patterns, excludes, cancellationToken);
-}
+namespace Ramstack.FileSystem;
+
+partial class VirtualFileSystemExtensions
+{
+ ///
+ /// Asynchronously returns an async-enumerable collection of file nodes (both directories and files)
+ /// in the specified path that match the given glob pattern.
+ ///
+ /// The file system to use.
+ /// The path to the directory to search.
+ /// The glob pattern to match against the names of file nodes.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ public static IAsyncEnumerable GetFileNodesAsync(this IVirtualFileSystem fs, string path, string pattern, CancellationToken cancellationToken = default) =>
+ fs.GetDirectory(path).GetFileNodesAsync(pattern, exclude: null, cancellationToken);
+
+ ///
+ /// Asynchronously returns an async-enumerable collection of file nodes (both directories and files)
+ /// in the specified path that match the given glob pattern.
+ ///
+ /// The file system to use.
+ /// The path to the directory to search.
+ /// The glob pattern to match against the names of file nodes.
+ /// An optional glob pattern to exclude certain file nodes.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ public static IAsyncEnumerable GetFileNodesAsync(this IVirtualFileSystem fs, string path, string pattern, string? exclude, CancellationToken cancellationToken = default) =>
+ fs.GetDirectory(path).GetFileNodesAsync([pattern], exclude is not null ? [exclude] : null, cancellationToken);
+
+ ///
+ /// Asynchronously returns an async-enumerable collection of file nodes (both directories and files)
+ /// in the specified path that match any of the given glob patterns.
+ ///
+ /// The file system to use.
+ /// The path to the directory to search.
+ /// An array of glob patterns to match against the names of file nodes.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ public static IAsyncEnumerable GetFileNodesAsync(this IVirtualFileSystem fs, string path, string[] patterns, CancellationToken cancellationToken = default) =>
+ fs.GetDirectory(path).GetFileNodesAsync(patterns, excludes: null, cancellationToken);
+
+ ///
+ /// Asynchronously returns an async-enumerable collection of file nodes (both directories and files)
+ /// in the specified path that match any of the given glob patterns.
+ ///
+ /// The file system to use.
+ /// The path to the directory to search.
+ /// An array of glob patterns to match against the names of file nodes.
+ /// An optional array of glob patterns to exclude file nodes.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ public static IAsyncEnumerable GetFileNodesAsync(this IVirtualFileSystem fs, string path, string[] patterns, string[]? excludes, CancellationToken cancellationToken = default) =>
+ fs.GetDirectory(path).GetFileNodesAsync(patterns, excludes, cancellationToken);
+
+ ///
+ /// Asynchronously returns an async-enumerable collection of files in the specified path that match the given glob pattern.
+ ///
+ /// The file system to use.
+ /// The path to the directory to search.
+ /// The glob pattern to match against the names of files.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ public static IAsyncEnumerable GetFilesAsync(this IVirtualFileSystem fs, string path, string pattern, CancellationToken cancellationToken = default) =>
+ fs.GetDirectory(path).GetFilesAsync(pattern, exclude: null, cancellationToken);
+
+ ///
+ /// Asynchronously returns an async-enumerable collection of files in the specified path that match the given glob pattern.
+ ///
+ /// The file system to use.
+ /// The path to the directory to search.
+ /// The glob pattern to match against the names of files.
+ /// An optional glob pattern to exclude certain files.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ public static IAsyncEnumerable GetFilesAsync(this IVirtualFileSystem fs, string path, string pattern, string? exclude, CancellationToken cancellationToken = default) =>
+ fs.GetDirectory(path).GetFilesAsync([pattern], exclude is not null ? [exclude] : null, cancellationToken);
+
+ ///
+ /// Asynchronously returns an async-enumerable collection of files in the specified path that match any of the given glob patterns.
+ ///
+ /// The file system to use.
+ /// The path to the directory to search.
+ /// An array of glob patterns to match against the names of files.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ public static IAsyncEnumerable GetFilesAsync(this IVirtualFileSystem fs, string path, string[] patterns, CancellationToken cancellationToken = default) =>
+ fs.GetDirectory(path).GetFilesAsync(patterns, excludes: null, cancellationToken);
+
+ ///
+ /// Asynchronously returns an async-enumerable collection of files in the specified path that match any of the given glob patterns.
+ ///
+ /// The file system to use.
+ /// The path to the directory to search.
+ /// An array of glob patterns to match against the names of files.
+ /// An optional array of glob patterns to exclude files.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ public static IAsyncEnumerable GetFilesAsync(this IVirtualFileSystem fs, string path, string[] patterns, string[]? excludes, CancellationToken cancellationToken = default) =>
+ fs.GetDirectory(path).GetFilesAsync(patterns, excludes, cancellationToken);
+
+ ///
+ /// Asynchronously returns an async-enumerable collection of directories in the specified path that match the given glob pattern.
+ ///
+ /// The file system to use.
+ /// The path to the directory to search.
+ /// The glob pattern to match against the names of directories.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ public static IAsyncEnumerable GetDirectoriesAsync(this IVirtualFileSystem fs, string path, string pattern, CancellationToken cancellationToken = default) =>
+ fs.GetDirectory(path).GetDirectoriesAsync(pattern, exclude: null, cancellationToken);
+
+ ///
+ /// Asynchronously returns an async-enumerable collection of directories in the specified path that match the given glob pattern.
+ ///
+ /// The file system to use.
+ /// The path to the directory to search.
+ /// The glob pattern to match against the names of directories.
+ /// An optional glob pattern to exclude certain directories.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ public static IAsyncEnumerable GetDirectoriesAsync(this IVirtualFileSystem fs, string path, string pattern, string? exclude, CancellationToken cancellationToken = default) =>
+ fs.GetDirectory(path).GetDirectoriesAsync([pattern], exclude is not null ? [exclude] : null, cancellationToken);
+
+ ///
+ /// Asynchronously returns an async-enumerable collection of directories in the specified path that match any of the given glob patterns.
+ ///
+ /// The file system to use.
+ /// The path to the directory to search.
+ /// An array of glob patterns to match against the names of directories.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ public static IAsyncEnumerable GetDirectoriesAsync(this IVirtualFileSystem fs, string path, string[] patterns, CancellationToken cancellationToken = default) =>
+ fs.GetDirectory(path).GetDirectoriesAsync(patterns, excludes: null, cancellationToken);
+
+ ///
+ /// Asynchronously returns an async-enumerable collection of directories in the specified path that match any of the given glob patterns.
+ ///
+ /// The file system to use.
+ /// The path to the directory to search.
+ /// An array of glob patterns to match against the names of directories.
+ /// An optional array of glob patterns to exclude directories.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ public static IAsyncEnumerable GetDirectoriesAsync(this IVirtualFileSystem fs, string path, string[] patterns, string[]? excludes, CancellationToken cancellationToken = default) =>
+ fs.GetDirectory(path).GetDirectoriesAsync(patterns, excludes, cancellationToken);
+}
diff --git a/src/Ramstack.FileSystem.Abstractions/VirtualFileSystemExtensions.cs b/src/Ramstack.FileSystem.Abstractions/VirtualFileSystemExtensions.cs
index 6239b78..59e42c2 100644
--- a/src/Ramstack.FileSystem.Abstractions/VirtualFileSystemExtensions.cs
+++ b/src/Ramstack.FileSystem.Abstractions/VirtualFileSystemExtensions.cs
@@ -1,405 +1,405 @@
-using System.Text;
-
-namespace Ramstack.FileSystem;
-
-///
-/// Provides extension methods for the interface.
-///
-public static partial class VirtualFileSystemExtensions
-{
- ///
- /// Asynchronously opens the file at the specified path for reading.
- ///
- /// The file system to use.
- /// The path of the file to open.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation, containing a stream for reading the file content.
- ///
- public static ValueTask OpenReadAsync(this IVirtualFileSystem fs, string path, CancellationToken cancellationToken = default) =>
- fs.GetFile(path).OpenReadAsync(cancellationToken);
-
- ///
- /// Asynchronously returns a with encoding for a file at the specified path.
- ///
- /// The file system to use.
- /// The path of the file to open.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation,
- /// containing a that reads from the text file.
- ///
- public static Task OpenTextAsync(this IVirtualFileSystem fs, string path, CancellationToken cancellationToken = default) =>
- fs.OpenTextAsync(path, Encoding.UTF8, cancellationToken);
-
- ///
- /// Asynchronously returns a with the specified character encoding for a file at the specified path.
- ///
- /// The file system to use.
- /// The path of the file to open.
- /// The encoding applied to the contents.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation,
- /// containing a that reads from the text file.
- ///
- public static async Task OpenTextAsync(this IVirtualFileSystem fs, string path, Encoding encoding, CancellationToken cancellationToken = default)
- {
- var stream = await fs.OpenReadAsync(path, cancellationToken).ConfigureAwait(false);
- return new StreamReader(stream, encoding, detectEncodingFromByteOrderMarks: true, bufferSize: -1, leaveOpen: false);
- }
-
- ///
- /// Asynchronously opens the file at the specified path for writing.
- ///
- /// The file system to use.
- /// The path of the file to open or create.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation, containing a stream for writing to the file content.
- ///
- public static ValueTask OpenWriteAsync(this IVirtualFileSystem fs, string path, CancellationToken cancellationToken = default) =>
- fs.GetFile(path).OpenWriteAsync(cancellationToken);
-
- ///
- /// Asynchronously writes the specified content to a file at the specified path. If the file exists, an exception will be thrown.
- ///
- /// The file system to use.
- /// The path of the file.
- /// A containing the content to write to the file.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- public static ValueTask WriteAsync(this IVirtualFileSystem fs, string path, Stream stream, CancellationToken cancellationToken = default) =>
- fs.GetFile(path).WriteAsync(stream, overwrite: false, cancellationToken);
-
- ///
- /// Asynchronously writes the specified content to a file at the specified path, creating a new file or overwriting an existing one.
- ///
- /// The file system to use.
- /// The path of the file.
- /// A containing the content to write to the file.
- /// to overwrite an existing file;
- /// to throw an exception if the file already exists.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- ///
- ///
- /// If the file does not exist, it will be created.
- /// If it exists and is , the existing file will be overwritten.
- /// If is and the file exists, an exception will be thrown.
- ///
- ///
- public static ValueTask WriteAsync(this IVirtualFileSystem fs, string path, Stream stream, bool overwrite, CancellationToken cancellationToken = default) =>
- fs.GetFile(path).WriteAsync(stream, overwrite, cancellationToken);
-
- ///
- /// Asynchronously reads all the text in the file.
- ///
- /// The file system to use.
- /// The file from which to read the entire text content.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation,
- /// containing the full text from the current file.
- ///
- public static ValueTask ReadAllTextAsync(this IVirtualFileSystem fs, string path, CancellationToken cancellationToken = default) =>
- fs.GetFile(path).ReadAllTextAsync(cancellationToken);
-
- ///
- /// Asynchronously reads all the text in the file with the specified encoding.
- ///
- /// The file system to use.
- /// The file from which to read the entire text content.
- /// The encoding applied to the contents.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation,
- /// containing the full text from the current file.
- ///
- public static ValueTask ReadAllTextAsync(this IVirtualFileSystem fs, string path, Encoding? encoding, CancellationToken cancellationToken = default) =>
- fs.GetFile(path).ReadAllTextAsync(encoding, cancellationToken);
-
- ///
- /// Asynchronously reads all lines of the file.
- ///
- /// The file system to use.
- /// The file to read from.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation,
- /// containing an array of all lines in the current file.
- ///
- public static ValueTask ReadAllLinesAsync(this IVirtualFileSystem fs, string path, CancellationToken cancellationToken = default) =>
- fs.GetFile(path).ReadAllLinesAsync(cancellationToken);
-
- ///
- /// Asynchronously reads all lines of the file with the specified encoding.
- ///
- /// The file system to use.
- /// The file to read from.
- /// The encoding applied to the contents.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation,
- /// containing an array of all lines in the current file.
- ///
- public static ValueTask ReadAllLinesAsync(this IVirtualFileSystem fs, string path, Encoding? encoding, CancellationToken cancellationToken = default) =>
- fs.GetFile(path).ReadAllLinesAsync(encoding, cancellationToken);
-
- ///
- /// Asynchronously reads the entire contents of the specified file into a byte array.
- ///
- /// The file system to use.
- /// The file to read from.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation,
- /// containing an array of the file's bytes.
- ///
- public static ValueTask ReadAllBytesAsync(this IVirtualFileSystem fs, string path, CancellationToken cancellationToken = default) =>
- fs.GetFile(path).ReadAllBytesAsync(cancellationToken);
-
- ///
- /// Asynchronously writes the specified string to the specified file. If the file already exists, it is truncated and overwritten.
- ///
- /// The file system to use.
- /// The file to write to.
- /// The contents to write to the file.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- public static ValueTask WriteAllTextAsync(this IVirtualFileSystem fs, string path, string contents, CancellationToken cancellationToken = default) =>
- fs.GetFile(path).WriteAllTextAsync(contents, cancellationToken);
-
- ///
- /// Asynchronously writes the specified string to the specified file. If the file already exists, it is truncated and overwritten.
- ///
- /// The file system to use.
- /// The file to write to.
- /// The contents to write to the file.
- /// The encoding to apply to the string.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- public static ValueTask WriteAllTextAsync(this IVirtualFileSystem fs, string path, string contents, Encoding? encoding, CancellationToken cancellationToken = default) =>
- fs.GetFile(path).WriteAllTextAsync(contents, encoding, cancellationToken);
-
- ///
- /// Asynchronously writes the specified string to the specified file. If the file already exists, it is truncated and overwritten.
- ///
- /// The file system to use.
- /// The file to write to.
- /// The contents to write to the file.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- public static ValueTask WriteAllTextAsync(this IVirtualFileSystem fs, string path, ReadOnlyMemory contents, CancellationToken cancellationToken = default) =>
- fs.GetFile(path).WriteAllTextAsync(contents, cancellationToken);
-
- ///
- /// Asynchronously writes the specified string to the specified file. If the file already exists, it is truncated and overwritten.
- ///
- /// The file system to use.
- /// The file to write to.
- /// The contents to write to the file.
- /// The encoding to apply to the string.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- public static ValueTask WriteAllTextAsync(this IVirtualFileSystem fs, string path, ReadOnlyMemory contents, Encoding? encoding, CancellationToken cancellationToken = default) =>
- fs.GetFile(path).WriteAllTextAsync(contents, encoding, cancellationToken);
-
- ///
- /// Asynchronously writes the specified lines to the specified file. If the file already exists, it is truncated and overwritten.
- ///
- /// The file system to use.
- /// The file to write to.
- /// The contents to write to the file.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- public static ValueTask WriteAllLinesAsync(this IVirtualFileSystem fs, string path, IEnumerable contents, CancellationToken cancellationToken = default) =>
- fs.GetFile(path).WriteAllLinesAsync(contents, cancellationToken);
-
- ///
- /// Asynchronously writes the specified lines to the specified file. If the file already exists, it is truncated and overwritten.
- ///
- /// The file system to use.
- /// The file to write to.
- /// The contents to write to the file.
- /// The encoding to apply to the string.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- public static ValueTask WriteAllLinesAsync(this IVirtualFileSystem fs, string path, IEnumerable contents, Encoding? encoding, CancellationToken cancellationToken = default) =>
- fs.GetFile(path).WriteAllLinesAsync(contents, encoding, cancellationToken);
-
- ///
- /// Asynchronously writes the specified byte array to the specified file. If the file already exists, it is truncated and overwritten.
- ///
- /// The file system to use.
- /// The file to write to.
- /// The bytes to write to the file.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- public static ValueTask WriteAllBytesAsync(this IVirtualFileSystem fs, string path, byte[] bytes, CancellationToken cancellationToken = default) =>
- fs.GetFile(path).WriteAllBytesAsync(bytes, cancellationToken);
-
- ///
- /// Asynchronously writes the specified byte array to the specified file. If the file already exists, it is truncated and overwritten.
- ///
- /// The file system to use.
- /// The file to write to.
- /// The bytes to write to the file.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- public static ValueTask WriteAllBytesAsync(this IVirtualFileSystem fs, string path, ReadOnlyMemory bytes, CancellationToken cancellationToken = default) =>
- fs.GetFile(path).WriteAllBytesAsync(bytes, cancellationToken);
-
- ///
- /// Asynchronously determines whether the file exists.
- ///
- /// The file system to use.
- /// The path of the file.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- /// The task result is if the file exists; otherwise, .
- ///
- public static ValueTask FileExistsAsync(this IVirtualFileSystem fs, string path, CancellationToken cancellationToken = default) =>
- fs.GetFile(path).ExistsAsync(cancellationToken);
-
- ///
- /// Asynchronously deletes the file at the specified path. No exception is thrown if the file does not exist.
- ///
- /// The file system to use.
- /// The path of the file to delete.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- public static ValueTask DeleteFileAsync(this IVirtualFileSystem fs, string path, CancellationToken cancellationToken = default) =>
- fs.GetFile(path).DeleteAsync(cancellationToken);
-
- ///
- /// Asynchronously copies a file within the file system to the specified destination path.
- ///
- /// The file system to use.
- /// The path of the file to copy.
- /// The path where the file will be copied to.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- public static ValueTask CopyFileAsync(this IVirtualFileSystem fs, string path, string destinationPath, CancellationToken cancellationToken = default) =>
- fs.GetFile(path).CopyToAsync(destinationPath, overwrite: false, cancellationToken);
-
- ///
- /// Asynchronously copies a file within the file system to the specified destination path.
- ///
- /// The file system to use.
- /// The path of the file to copy.
- /// The path where the file will be copied to.
- /// to overwrite an existing file; to throw an exception if the file already exists.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- ///
- ///
- /// If the file does not exist, it will be created.
- /// If it exists and is , the existing file will be overwritten.
- /// If is and the file exists, an exception will be thrown.
- ///
- ///
- public static ValueTask CopyFileAsync(this IVirtualFileSystem fs, string path, string destinationPath, bool overwrite, CancellationToken cancellationToken = default) =>
- fs.GetFile(path).CopyToAsync(destinationPath, overwrite, cancellationToken);
-
- ///
- /// Asynchronously determines whether the directory exists.
- ///
- /// The file system to use.
- /// The path of the directory.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- /// The task result is if the directory exists; otherwise, .
- ///
- public static ValueTask DirectoryExistsAsync(this IVirtualFileSystem fs, string path, CancellationToken cancellationToken = default) =>
- fs.GetDirectory(path).ExistsAsync(cancellationToken);
-
- ///
- /// Asynchronously creates a directory at the specified path.
- ///
- /// The file system to use.
- /// The path of the directory to create.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- public static ValueTask CreateDirectoryAsync(this IVirtualFileSystem fs, string path, CancellationToken cancellationToken = default) =>
- fs.GetDirectory(path).CreateAsync(cancellationToken);
-
- ///
- /// Asynchronously deletes a directory at the specified path, including its subdirectories and all files.
- ///
- /// The file system to use.
- /// The path of the directory to delete.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- ///
- public static ValueTask DeleteDirectoryAsync(this IVirtualFileSystem fs, string path, CancellationToken cancellationToken = default) =>
- fs.GetDirectory(path).DeleteAsync(cancellationToken);
-
- ///
- /// Asynchronously returns an async-enumerable collection of file nodes (both directories and files)
- /// within the directory at the specified path.
- ///
- /// The file system to use.
- /// The path of the directory.
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- public static IAsyncEnumerable GetFileNodesAsync(this IVirtualFileSystem fs, string path, CancellationToken cancellationToken = default) =>
- fs.GetDirectory(path).GetFileNodesAsync(cancellationToken);
-
- ///
- /// Asynchronously returns an async-enumerable collection of files within the directory at the specified path.
- ///
- /// The file system to use.
- /// The path of the directory.
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- public static IAsyncEnumerable GetFilesAsync(this IVirtualFileSystem fs, string path, CancellationToken cancellationToken = default) =>
- fs.GetDirectory(path).GetFilesAsync(cancellationToken);
-
- ///
- /// Asynchronously returns an async-enumerable collection of directories within the directory at the specified path.
- ///
- /// The file system to use.
- /// The path of the directory.
- /// An optional cancellation token to cancel the operation.
- ///
- /// An async-enumerable collection of instances.
- ///
- public static IAsyncEnumerable GetDirectoriesAsync(this IVirtualFileSystem fs, string path, CancellationToken cancellationToken = default) =>
- fs.GetDirectory(path).GetDirectoriesAsync(cancellationToken);
-}
+using System.Text;
+
+namespace Ramstack.FileSystem;
+
+///
+/// Provides extension methods for the interface.
+///
+public static partial class VirtualFileSystemExtensions
+{
+ ///
+ /// Asynchronously opens the file at the specified path for reading.
+ ///
+ /// The file system to use.
+ /// The path of the file to open.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation, containing a stream for reading the file content.
+ ///
+ public static ValueTask OpenReadAsync(this IVirtualFileSystem fs, string path, CancellationToken cancellationToken = default) =>
+ fs.GetFile(path).OpenReadAsync(cancellationToken);
+
+ ///
+ /// Asynchronously returns a with encoding for a file at the specified path.
+ ///
+ /// The file system to use.
+ /// The path of the file to open.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation,
+ /// containing a that reads from the text file.
+ ///
+ public static Task OpenTextAsync(this IVirtualFileSystem fs, string path, CancellationToken cancellationToken = default) =>
+ fs.OpenTextAsync(path, Encoding.UTF8, cancellationToken);
+
+ ///
+ /// Asynchronously returns a with the specified character encoding for a file at the specified path.
+ ///
+ /// The file system to use.
+ /// The path of the file to open.
+ /// The encoding applied to the contents.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation,
+ /// containing a that reads from the text file.
+ ///
+ public static async Task OpenTextAsync(this IVirtualFileSystem fs, string path, Encoding encoding, CancellationToken cancellationToken = default)
+ {
+ var stream = await fs.OpenReadAsync(path, cancellationToken).ConfigureAwait(false);
+ return new StreamReader(stream, encoding, detectEncodingFromByteOrderMarks: true, bufferSize: -1, leaveOpen: false);
+ }
+
+ ///
+ /// Asynchronously opens the file at the specified path for writing.
+ ///
+ /// The file system to use.
+ /// The path of the file to open or create.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation, containing a stream for writing to the file content.
+ ///
+ public static ValueTask OpenWriteAsync(this IVirtualFileSystem fs, string path, CancellationToken cancellationToken = default) =>
+ fs.GetFile(path).OpenWriteAsync(cancellationToken);
+
+ ///
+ /// Asynchronously writes the specified content to a file at the specified path. If the file exists, an exception will be thrown.
+ ///
+ /// The file system to use.
+ /// The path of the file.
+ /// A containing the content to write to the file.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ public static ValueTask WriteAsync(this IVirtualFileSystem fs, string path, Stream stream, CancellationToken cancellationToken = default) =>
+ fs.GetFile(path).WriteAsync(stream, overwrite: false, cancellationToken);
+
+ ///
+ /// Asynchronously writes the specified content to a file at the specified path, creating a new file or overwriting an existing one.
+ ///
+ /// The file system to use.
+ /// The path of the file.
+ /// A containing the content to write to the file.
+ /// to overwrite an existing file;
+ /// to throw an exception if the file already exists.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ ///
+ ///
+ /// If the file does not exist, it will be created.
+ /// If it exists and is , the existing file will be overwritten.
+ /// If is and the file exists, an exception will be thrown.
+ ///
+ ///
+ public static ValueTask WriteAsync(this IVirtualFileSystem fs, string path, Stream stream, bool overwrite, CancellationToken cancellationToken = default) =>
+ fs.GetFile(path).WriteAsync(stream, overwrite, cancellationToken);
+
+ ///
+ /// Asynchronously reads all the text in the file.
+ ///
+ /// The file system to use.
+ /// The file from which to read the entire text content.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation,
+ /// containing the full text from the current file.
+ ///
+ public static ValueTask ReadAllTextAsync(this IVirtualFileSystem fs, string path, CancellationToken cancellationToken = default) =>
+ fs.GetFile(path).ReadAllTextAsync(cancellationToken);
+
+ ///
+ /// Asynchronously reads all the text in the file with the specified encoding.
+ ///
+ /// The file system to use.
+ /// The file from which to read the entire text content.
+ /// The encoding applied to the contents.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation,
+ /// containing the full text from the current file.
+ ///
+ public static ValueTask ReadAllTextAsync(this IVirtualFileSystem fs, string path, Encoding? encoding, CancellationToken cancellationToken = default) =>
+ fs.GetFile(path).ReadAllTextAsync(encoding, cancellationToken);
+
+ ///
+ /// Asynchronously reads all lines of the file.
+ ///
+ /// The file system to use.
+ /// The file to read from.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation,
+ /// containing an array of all lines in the current file.
+ ///
+ public static ValueTask ReadAllLinesAsync(this IVirtualFileSystem fs, string path, CancellationToken cancellationToken = default) =>
+ fs.GetFile(path).ReadAllLinesAsync(cancellationToken);
+
+ ///
+ /// Asynchronously reads all lines of the file with the specified encoding.
+ ///
+ /// The file system to use.
+ /// The file to read from.
+ /// The encoding applied to the contents.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation,
+ /// containing an array of all lines in the current file.
+ ///
+ public static ValueTask ReadAllLinesAsync(this IVirtualFileSystem fs, string path, Encoding? encoding, CancellationToken cancellationToken = default) =>
+ fs.GetFile(path).ReadAllLinesAsync(encoding, cancellationToken);
+
+ ///
+ /// Asynchronously reads the entire contents of the specified file into a byte array.
+ ///
+ /// The file system to use.
+ /// The file to read from.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation,
+ /// containing an array of the file's bytes.
+ ///
+ public static ValueTask ReadAllBytesAsync(this IVirtualFileSystem fs, string path, CancellationToken cancellationToken = default) =>
+ fs.GetFile(path).ReadAllBytesAsync(cancellationToken);
+
+ ///
+ /// Asynchronously writes the specified string to the specified file. If the file already exists, it is truncated and overwritten.
+ ///
+ /// The file system to use.
+ /// The file to write to.
+ /// The contents to write to the file.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ public static ValueTask WriteAllTextAsync(this IVirtualFileSystem fs, string path, string contents, CancellationToken cancellationToken = default) =>
+ fs.GetFile(path).WriteAllTextAsync(contents, cancellationToken);
+
+ ///
+ /// Asynchronously writes the specified string to the specified file. If the file already exists, it is truncated and overwritten.
+ ///
+ /// The file system to use.
+ /// The file to write to.
+ /// The contents to write to the file.
+ /// The encoding to apply to the string.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ public static ValueTask WriteAllTextAsync(this IVirtualFileSystem fs, string path, string contents, Encoding? encoding, CancellationToken cancellationToken = default) =>
+ fs.GetFile(path).WriteAllTextAsync(contents, encoding, cancellationToken);
+
+ ///
+ /// Asynchronously writes the specified string to the specified file. If the file already exists, it is truncated and overwritten.
+ ///
+ /// The file system to use.
+ /// The file to write to.
+ /// The contents to write to the file.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ public static ValueTask WriteAllTextAsync(this IVirtualFileSystem fs, string path, ReadOnlyMemory contents, CancellationToken cancellationToken = default) =>
+ fs.GetFile(path).WriteAllTextAsync(contents, cancellationToken);
+
+ ///
+ /// Asynchronously writes the specified string to the specified file. If the file already exists, it is truncated and overwritten.
+ ///
+ /// The file system to use.
+ /// The file to write to.
+ /// The contents to write to the file.
+ /// The encoding to apply to the string.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ public static ValueTask WriteAllTextAsync(this IVirtualFileSystem fs, string path, ReadOnlyMemory contents, Encoding? encoding, CancellationToken cancellationToken = default) =>
+ fs.GetFile(path).WriteAllTextAsync(contents, encoding, cancellationToken);
+
+ ///
+ /// Asynchronously writes the specified lines to the specified file. If the file already exists, it is truncated and overwritten.
+ ///
+ /// The file system to use.
+ /// The file to write to.
+ /// The contents to write to the file.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ public static ValueTask WriteAllLinesAsync(this IVirtualFileSystem fs, string path, IEnumerable contents, CancellationToken cancellationToken = default) =>
+ fs.GetFile(path).WriteAllLinesAsync(contents, cancellationToken);
+
+ ///
+ /// Asynchronously writes the specified lines to the specified file. If the file already exists, it is truncated and overwritten.
+ ///
+ /// The file system to use.
+ /// The file to write to.
+ /// The contents to write to the file.
+ /// The encoding to apply to the string.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ public static ValueTask WriteAllLinesAsync(this IVirtualFileSystem fs, string path, IEnumerable contents, Encoding? encoding, CancellationToken cancellationToken = default) =>
+ fs.GetFile(path).WriteAllLinesAsync(contents, encoding, cancellationToken);
+
+ ///
+ /// Asynchronously writes the specified byte array to the specified file. If the file already exists, it is truncated and overwritten.
+ ///
+ /// The file system to use.
+ /// The file to write to.
+ /// The bytes to write to the file.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ public static ValueTask WriteAllBytesAsync(this IVirtualFileSystem fs, string path, byte[] bytes, CancellationToken cancellationToken = default) =>
+ fs.GetFile(path).WriteAllBytesAsync(bytes, cancellationToken);
+
+ ///
+ /// Asynchronously writes the specified byte array to the specified file. If the file already exists, it is truncated and overwritten.
+ ///
+ /// The file system to use.
+ /// The file to write to.
+ /// The bytes to write to the file.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ public static ValueTask WriteAllBytesAsync(this IVirtualFileSystem fs, string path, ReadOnlyMemory bytes, CancellationToken cancellationToken = default) =>
+ fs.GetFile(path).WriteAllBytesAsync(bytes, cancellationToken);
+
+ ///
+ /// Asynchronously determines whether the file exists.
+ ///
+ /// The file system to use.
+ /// The path of the file.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ /// The task result is if the file exists; otherwise, .
+ ///
+ public static ValueTask FileExistsAsync(this IVirtualFileSystem fs, string path, CancellationToken cancellationToken = default) =>
+ fs.GetFile(path).ExistsAsync(cancellationToken);
+
+ ///
+ /// Asynchronously deletes the file at the specified path. No exception is thrown if the file does not exist.
+ ///
+ /// The file system to use.
+ /// The path of the file to delete.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ public static ValueTask DeleteFileAsync(this IVirtualFileSystem fs, string path, CancellationToken cancellationToken = default) =>
+ fs.GetFile(path).DeleteAsync(cancellationToken);
+
+ ///
+ /// Asynchronously copies a file within the file system to the specified destination path.
+ ///
+ /// The file system to use.
+ /// The path of the file to copy.
+ /// The path where the file will be copied to.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ public static ValueTask CopyFileAsync(this IVirtualFileSystem fs, string path, string destinationPath, CancellationToken cancellationToken = default) =>
+ fs.GetFile(path).CopyToAsync(destinationPath, overwrite: false, cancellationToken);
+
+ ///
+ /// Asynchronously copies a file within the file system to the specified destination path.
+ ///
+ /// The file system to use.
+ /// The path of the file to copy.
+ /// The path where the file will be copied to.
+ /// to overwrite an existing file; to throw an exception if the file already exists.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ ///
+ ///
+ /// If the file does not exist, it will be created.
+ /// If it exists and is , the existing file will be overwritten.
+ /// If is and the file exists, an exception will be thrown.
+ ///
+ ///
+ public static ValueTask CopyFileAsync(this IVirtualFileSystem fs, string path, string destinationPath, bool overwrite, CancellationToken cancellationToken = default) =>
+ fs.GetFile(path).CopyToAsync(destinationPath, overwrite, cancellationToken);
+
+ ///
+ /// Asynchronously determines whether the directory exists.
+ ///
+ /// The file system to use.
+ /// The path of the directory.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ /// The task result is if the directory exists; otherwise, .
+ ///
+ public static ValueTask DirectoryExistsAsync(this IVirtualFileSystem fs, string path, CancellationToken cancellationToken = default) =>
+ fs.GetDirectory(path).ExistsAsync(cancellationToken);
+
+ ///
+ /// Asynchronously creates a directory at the specified path.
+ ///
+ /// The file system to use.
+ /// The path of the directory to create.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ public static ValueTask CreateDirectoryAsync(this IVirtualFileSystem fs, string path, CancellationToken cancellationToken = default) =>
+ fs.GetDirectory(path).CreateAsync(cancellationToken);
+
+ ///
+ /// Asynchronously deletes a directory at the specified path, including its subdirectories and all files.
+ ///
+ /// The file system to use.
+ /// The path of the directory to delete.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ ///
+ public static ValueTask DeleteDirectoryAsync(this IVirtualFileSystem fs, string path, CancellationToken cancellationToken = default) =>
+ fs.GetDirectory(path).DeleteAsync(cancellationToken);
+
+ ///
+ /// Asynchronously returns an async-enumerable collection of file nodes (both directories and files)
+ /// within the directory at the specified path.
+ ///
+ /// The file system to use.
+ /// The path of the directory.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ public static IAsyncEnumerable GetFileNodesAsync(this IVirtualFileSystem fs, string path, CancellationToken cancellationToken = default) =>
+ fs.GetDirectory(path).GetFileNodesAsync(cancellationToken);
+
+ ///
+ /// Asynchronously returns an async-enumerable collection of files within the directory at the specified path.
+ ///
+ /// The file system to use.
+ /// The path of the directory.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ public static IAsyncEnumerable GetFilesAsync(this IVirtualFileSystem fs, string path, CancellationToken cancellationToken = default) =>
+ fs.GetDirectory(path).GetFilesAsync(cancellationToken);
+
+ ///
+ /// Asynchronously returns an async-enumerable collection of directories within the directory at the specified path.
+ ///
+ /// The file system to use.
+ /// The path of the directory.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// An async-enumerable collection of instances.
+ ///
+ public static IAsyncEnumerable GetDirectoriesAsync(this IVirtualFileSystem fs, string path, CancellationToken cancellationToken = default) =>
+ fs.GetDirectory(path).GetDirectoriesAsync(cancellationToken);
+}
diff --git a/src/Ramstack.FileSystem.Abstractions/VirtualNode.cs b/src/Ramstack.FileSystem.Abstractions/VirtualNode.cs
index f83ca8b..a51a590 100644
--- a/src/Ramstack.FileSystem.Abstractions/VirtualNode.cs
+++ b/src/Ramstack.FileSystem.Abstractions/VirtualNode.cs
@@ -1,154 +1,154 @@
-namespace Ramstack.FileSystem;
-
-///
-/// Represents a node in a virtual file system, which could be either a file or a directory.
-///
-public abstract class VirtualNode
-{
- private VirtualNodeProperties? _properties;
-
- ///
- /// Gets the instance associated with this node.
- ///
- public abstract IVirtualFileSystem FileSystem { get; }
-
- ///
- /// Gets a value indicating whether the file or directory is read-only.
- ///
- public bool IsReadOnly => FileSystem.IsReadOnly;
-
- ///
- /// Gets the full path of the file or directory.
- ///
- public string FullName { get; }
-
- ///
- /// Gets the name of the file or directory, excluding the path.
- ///
- public string Name => VirtualPath.GetFileName(FullName);
-
- ///
- /// Gets the file extension, including the leading dot ., or an empty string if there is no extension.
- ///
- public string Extension => VirtualPath.GetExtension(FullName);
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The full path of the file or directory.
- protected VirtualNode(string path)
- {
- Debug.Assert(VirtualPath.IsNormalized(path));
- FullName = path;
- }
-
- ///
- /// Initializes a new instance of the class with specified properties.
- ///
- /// The full path of the file or directory.
- /// The properties of the file or directory.
- protected VirtualNode(string path, VirtualNodeProperties? properties) : this(path) =>
- _properties = properties;
-
- ///
- /// Refreshes the state of the file or directory node, clearing any cached or stored values.
- ///
- public void Refresh()
- {
- _properties = null;
- RefreshCore();
- }
-
- ///
- /// Asynchronously determines whether the file or directory exists.
- ///
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- /// The task result is if the file or directory exists; otherwise, .
- ///
- public ValueTask ExistsAsync(CancellationToken cancellationToken = default)
- {
- if (TryGetProperties(out var properties))
- return new ValueTask(properties.Exists);
-
- return ExistsCoreAsync(cancellationToken);
- }
-
- ///
- /// Asynchronously retrieves the properties of the file or directory.
- ///
- /// A boolean indicating whether to refresh the state before retrieving the properties.
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation,
- /// with a result of type containing the file or directory properties.
- ///
- public ValueTask GetPropertiesAsync(bool refresh, CancellationToken cancellationToken = default)
- {
- var properties = _properties;
- return refresh || properties is null
- ? GetPropertiesImpl(cancellationToken)
- : new ValueTask(properties);
-
- async ValueTask GetPropertiesImpl(CancellationToken token) =>
- _properties = await GetPropertiesCoreAsync(token).ConfigureAwait(false) ?? VirtualNodeProperties.Unavailable;
- }
-
- ///
- /// Core implementation for refreshing the state of the file or directory node,
- /// clearing any cached or stored values.
- ///
- protected virtual void RefreshCore()
- {
- }
-
- ///
- /// Core implementation for asynchronously retrieving the current properties of the file or directory.
- ///
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation,
- /// with a result of type containing the updated properties,
- /// or if the properties cannot be retrieved.
- ///
- protected abstract ValueTask GetPropertiesCoreAsync(CancellationToken cancellationToken);
-
- ///
- /// Core implementation for asynchronously determining whether the file or directory exists.
- ///
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation.
- /// The task result is if the file or directory exists; otherwise, .
- ///
- protected virtual async ValueTask ExistsCoreAsync(CancellationToken cancellationToken) =>
- (await GetPropertiesAsync(refresh: true, cancellationToken).ConfigureAwait(false)).Exists;
-
- ///
- /// Attempts to retrieve the current properties of the file or directory.
- ///
- /// When this method returns, contains the current if available;
- /// otherwise, .
- ///
- /// if the properties were successfully retrieved;
- /// otherwise, if the properties have not been set or have been invalidated.
- ///
- protected bool TryGetProperties([NotNullWhen(true)] out VirtualNodeProperties? properties)
- {
- properties = _properties;
- return properties is not null;
- }
-
- ///
- /// Ensures that the current file or directory is writable.
- ///
- /// Thrown when the current file or directory is read-only
- /// and modifications are not supported.
- ///
- protected void EnsureWritable()
- {
- if (FileSystem.IsReadOnly)
- throw new NotSupportedException("Write operations are not supported on a read-only instance.");
- }
-}
+namespace Ramstack.FileSystem;
+
+///
+/// Represents a node in a virtual file system, which could be either a file or a directory.
+///
+public abstract class VirtualNode
+{
+ private VirtualNodeProperties? _properties;
+
+ ///
+ /// Gets the instance associated with this node.
+ ///
+ public abstract IVirtualFileSystem FileSystem { get; }
+
+ ///
+ /// Gets a value indicating whether the file or directory is read-only.
+ ///
+ public bool IsReadOnly => FileSystem.IsReadOnly;
+
+ ///
+ /// Gets the full path of the file or directory.
+ ///
+ public string FullName { get; }
+
+ ///
+ /// Gets the name of the file or directory, excluding the path.
+ ///
+ public string Name => VirtualPath.GetFileName(FullName);
+
+ ///
+ /// Gets the file extension, including the leading dot ., or an empty string if there is no extension.
+ ///
+ public string Extension => VirtualPath.GetExtension(FullName);
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The full path of the file or directory.
+ protected VirtualNode(string path)
+ {
+ Debug.Assert(VirtualPath.IsNormalized(path));
+ FullName = path;
+ }
+
+ ///
+ /// Initializes a new instance of the class with specified properties.
+ ///
+ /// The full path of the file or directory.
+ /// The properties of the file or directory.
+ protected VirtualNode(string path, VirtualNodeProperties? properties) : this(path) =>
+ _properties = properties;
+
+ ///
+ /// Refreshes the state of the file or directory node, clearing any cached or stored values.
+ ///
+ public void Refresh()
+ {
+ _properties = null;
+ RefreshCore();
+ }
+
+ ///
+ /// Asynchronously determines whether the file or directory exists.
+ ///
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ /// The task result is if the file or directory exists; otherwise, .
+ ///
+ public ValueTask ExistsAsync(CancellationToken cancellationToken = default)
+ {
+ if (TryGetProperties(out var properties))
+ return new ValueTask(properties.Exists);
+
+ return ExistsCoreAsync(cancellationToken);
+ }
+
+ ///
+ /// Asynchronously retrieves the properties of the file or directory.
+ ///
+ /// A boolean indicating whether to refresh the state before retrieving the properties.
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation,
+ /// with a result of type containing the file or directory properties.
+ ///
+ public ValueTask GetPropertiesAsync(bool refresh, CancellationToken cancellationToken = default)
+ {
+ var properties = _properties;
+ return refresh || properties is null
+ ? GetPropertiesImpl(cancellationToken)
+ : new ValueTask(properties);
+
+ async ValueTask GetPropertiesImpl(CancellationToken token) =>
+ _properties = await GetPropertiesCoreAsync(token).ConfigureAwait(false) ?? VirtualNodeProperties.Unavailable;
+ }
+
+ ///
+ /// Core implementation for refreshing the state of the file or directory node,
+ /// clearing any cached or stored values.
+ ///
+ protected virtual void RefreshCore()
+ {
+ }
+
+ ///
+ /// Core implementation for asynchronously retrieving the current properties of the file or directory.
+ ///
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation,
+ /// with a result of type containing the updated properties,
+ /// or if the properties cannot be retrieved.
+ ///
+ protected abstract ValueTask GetPropertiesCoreAsync(CancellationToken cancellationToken);
+
+ ///
+ /// Core implementation for asynchronously determining whether the file or directory exists.
+ ///
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation.
+ /// The task result is if the file or directory exists; otherwise, .
+ ///
+ protected virtual async ValueTask ExistsCoreAsync(CancellationToken cancellationToken) =>
+ (await GetPropertiesAsync(refresh: true, cancellationToken).ConfigureAwait(false)).Exists;
+
+ ///
+ /// Attempts to retrieve the current properties of the file or directory.
+ ///
+ /// When this method returns, contains the current if available;
+ /// otherwise, .
+ ///
+ /// if the properties were successfully retrieved;
+ /// otherwise, if the properties have not been set or have been invalidated.
+ ///
+ protected bool TryGetProperties([NotNullWhen(true)] out VirtualNodeProperties? properties)
+ {
+ properties = _properties;
+ return properties is not null;
+ }
+
+ ///
+ /// Ensures that the current file or directory is writable.
+ ///
+ /// Thrown when the current file or directory is read-only
+ /// and modifications are not supported.
+ ///
+ protected void EnsureWritable()
+ {
+ if (FileSystem.IsReadOnly)
+ throw new NotSupportedException("Write operations are not supported on a read-only instance.");
+ }
+}
diff --git a/src/Ramstack.FileSystem.Abstractions/VirtualNodeExtensions.cs b/src/Ramstack.FileSystem.Abstractions/VirtualNodeExtensions.cs
index cf3cb4e..3fb3f56 100644
--- a/src/Ramstack.FileSystem.Abstractions/VirtualNodeExtensions.cs
+++ b/src/Ramstack.FileSystem.Abstractions/VirtualNodeExtensions.cs
@@ -1,19 +1,19 @@
-namespace Ramstack.FileSystem;
-
-///
-/// Provides extension methods for the class.
-///
-public static class VirtualNodeExtensions
-{
- ///
- /// Asynchronously retrieves the properties of the specified file or directory.
- ///
- /// The source file or directory for which to get the .
- /// An optional cancellation token to cancel the operation.
- ///
- /// A representing the asynchronous operation,
- /// with a result of type containing the node properties.
- ///
- public static ValueTask GetPropertiesAsync(this VirtualNode node, CancellationToken cancellationToken = default) =>
- node.GetPropertiesAsync(refresh: false, cancellationToken);
-}
+namespace Ramstack.FileSystem;
+
+///
+/// Provides extension methods for the class.
+///
+public static class VirtualNodeExtensions
+{
+ ///
+ /// Asynchronously retrieves the properties of the specified file or directory.
+ ///
+ /// The source file or directory for which to get the .
+ /// An optional cancellation token to cancel the operation.
+ ///
+ /// A representing the asynchronous operation,
+ /// with a result of type containing the node properties.
+ ///
+ public static ValueTask GetPropertiesAsync(this VirtualNode node, CancellationToken cancellationToken = default) =>
+ node.GetPropertiesAsync(refresh: false, cancellationToken);
+}
diff --git a/src/Ramstack.FileSystem.Abstractions/VirtualNodeProperties.cs b/src/Ramstack.FileSystem.Abstractions/VirtualNodeProperties.cs
index 32bd808..a2d7903 100644
--- a/src/Ramstack.FileSystem.Abstractions/VirtualNodeProperties.cs
+++ b/src/Ramstack.FileSystem.Abstractions/VirtualNodeProperties.cs
@@ -1,92 +1,92 @@
-namespace Ramstack.FileSystem;
-
-///
-/// Represents the properties of a file node, which can be either a file or a directory.
-///
-[DebuggerDisplay("{ToStringDebugger(),nq}")]
-public sealed class VirtualNodeProperties
-{
- ///
- /// Gets an instance of that represents a node with an unavailable state.
- ///
- public static VirtualNodeProperties Unavailable { get; } =
- new VirtualNodeProperties(default, default, default, -1);
-
- ///
- /// Gets an instance of that represents a node with no data.
- ///
- public static VirtualNodeProperties None { get; } =
- new VirtualNodeProperties(default, default, default, 0);
-
- ///
- /// Gets the time when the current file or directory was created.
- ///
- public DateTimeOffset CreationTime { get; }
-
- ///
- /// Gets the time when the current file or directory was last accessed.
- ///
- public DateTimeOffset LastAccessTime { get; }
-
- ///
- /// Gets the time when the current file or directory was last modified.
- ///
- public DateTimeOffset LastWriteTime { get; }
-
- ///
- /// Gets the size of the file in bytes.
- /// Returns 0 for directories and -1 for non-existent nodes.
- ///
- public long Length { get; }
-
- ///
- /// Gets a value indicating whether the current file or directory exists.
- /// Returns if the node exists; otherwise, .
- ///
- public bool Exists => Length >= 0;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The time when the node was created.
- /// The time when the node was last accessed.
- /// The time when the node was last modified.
- /// The size of the file in bytes, or 0 for directories, or -1 for non-existent nodes.
- private VirtualNodeProperties(DateTimeOffset creationTime, DateTimeOffset lastAccessTime, DateTimeOffset lastWriteTime, long length) =>
- (CreationTime, LastAccessTime, LastWriteTime, Length) = (creationTime, lastAccessTime, lastWriteTime, length);
-
- ///
- /// Creates a new instance of for a file.
- ///
- /// The time when the file was created.
- /// The time when the file was last accessed.
- /// The time when the file was last modified.
- /// The size of the file in bytes.
- ///
- /// A new instance representing a file.
- ///
- public static VirtualNodeProperties CreateFileProperties(DateTimeOffset creationTime, DateTimeOffset lastAccessTime, DateTimeOffset lastWriteTime, long length) =>
- new VirtualNodeProperties(creationTime, lastAccessTime, lastWriteTime, length);
-
- ///
- /// Creates a new instance of for a directory.
- ///
- /// The time when the directory was created.
- /// The time when the directory was last accessed.
- /// The time when the directory was last modified.
- ///
- /// A new instance representing a directory.
- ///
- public static VirtualNodeProperties CreateDirectoryProperties(DateTimeOffset creationTime, DateTimeOffset lastAccessTime, DateTimeOffset lastWriteTime) =>
- new VirtualNodeProperties(creationTime, lastAccessTime, lastWriteTime, 0);
-
- ///
- /// Returns a string representation of the current instance for debugging purposes.
- ///
- ///
- /// A string that provides information about the length of the current instance.
- /// If the length is -1, it returns "Unavailable".
- ///
- private string ToStringDebugger() =>
- Length >= 0 ? $"Length = {Length}" : "Unavailable";
-}
+namespace Ramstack.FileSystem;
+
+///
+/// Represents the properties of a file node, which can be either a file or a directory.
+///
+[DebuggerDisplay("{ToStringDebugger(),nq}")]
+public sealed class VirtualNodeProperties
+{
+ ///
+ /// Gets an instance of that represents a node with an unavailable state.
+ ///
+ public static VirtualNodeProperties Unavailable { get; } =
+ new VirtualNodeProperties(default, default, default, -1);
+
+ ///
+ /// Gets an instance of that represents a node with no data.
+ ///
+ public static VirtualNodeProperties None { get; } =
+ new VirtualNodeProperties(default, default, default, 0);
+
+ ///
+ /// Gets the time when the current file or directory was created.
+ ///
+ public DateTimeOffset CreationTime { get; }
+
+ ///
+ /// Gets the time when the current file or directory was last accessed.
+ ///
+ public DateTimeOffset LastAccessTime { get; }
+
+ ///
+ /// Gets the time when the current file or directory was last modified.
+ ///
+ public DateTimeOffset LastWriteTime { get; }
+
+ ///
+ /// Gets the size of the file in bytes.
+ /// Returns 0 for directories and -1 for non-existent nodes.
+ ///
+ public long Length { get; }
+
+ ///
+ /// Gets a value indicating whether the current file or directory exists.
+ /// Returns if the node exists; otherwise, .
+ ///
+ public bool Exists => Length >= 0;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The time when the node was created.
+ /// The time when the node was last accessed.
+ /// The time when the node was last modified.
+ /// The size of the file in bytes, or 0 for directories, or -1 for non-existent nodes.
+ private VirtualNodeProperties(DateTimeOffset creationTime, DateTimeOffset lastAccessTime, DateTimeOffset lastWriteTime, long length) =>
+ (CreationTime, LastAccessTime, LastWriteTime, Length) = (creationTime, lastAccessTime, lastWriteTime, length);
+
+ ///
+ /// Creates a new instance of for a file.
+ ///
+ /// The time when the file was created.
+ /// The time when the file was last accessed.
+ /// The time when the file was last modified.
+ /// The size of the file in bytes.
+ ///
+ /// A new instance representing a file.
+ ///
+ public static VirtualNodeProperties CreateFileProperties(DateTimeOffset creationTime, DateTimeOffset lastAccessTime, DateTimeOffset lastWriteTime, long length) =>
+ new VirtualNodeProperties(creationTime, lastAccessTime, lastWriteTime, length);
+
+ ///
+ /// Creates a new instance of for a directory.
+ ///
+ /// The time when the directory was created.
+ /// The time when the directory was last accessed.
+ /// The time when the directory was last modified.
+ ///
+ /// A new instance representing a directory.
+ ///
+ public static VirtualNodeProperties CreateDirectoryProperties(DateTimeOffset creationTime, DateTimeOffset lastAccessTime, DateTimeOffset lastWriteTime) =>
+ new VirtualNodeProperties(creationTime, lastAccessTime, lastWriteTime, 0);
+
+ ///
+ /// Returns a string representation of the current instance for debugging purposes.
+ ///
+ ///
+ /// A string that provides information about the length of the current instance.
+ /// If the length is -1, it returns "Unavailable".
+ ///
+ private string ToStringDebugger() =>
+ Length >= 0 ? $"Length = {Length}" : "Unavailable";
+}
diff --git a/src/Ramstack.FileSystem.Abstractions/VirtualPath.cs b/src/Ramstack.FileSystem.Abstractions/VirtualPath.cs
index dd75453..5648368 100644
--- a/src/Ramstack.FileSystem.Abstractions/VirtualPath.cs
+++ b/src/Ramstack.FileSystem.Abstractions/VirtualPath.cs
@@ -1,401 +1,401 @@
-using System.Buffers;
-
-using Ramstack.FileSystem.Utilities;
-
-namespace Ramstack.FileSystem;
-
-///
-/// Provides utility methods for working with virtual paths.
-///
-///
-///
-/// For compatibility across different implementations of
-/// and operating systems, directory separators are unified to use both
-/// backslashes and forward slashes ("/" and "\").
-///
-///
-/// This approach will be reviewed once a better solution is found.
-///
-///
-/// When normalizing paths by using the method ,
-/// backslashes ("\") will be replaced with forward slashes ("/") forcibly.
-///
-///
-public static class VirtualPath
-{
- ///
- /// The threshold size in characters for using stack allocation.
- ///
- private const int StackallocThreshold = 256;
-
- ///
- /// Returns an extension (including the period ".") of the specified path string.
- ///
- /// The path string from which to get the extension.
- ///
- /// The extension of the specified path (including the period "."),
- /// or an empty string if no extension is present.
- ///
- ///
- /// returns an empty string ("")
- /// if the extension consists solely of a period (e.g., "file."), which differs from
- /// , which returns "." in this case.
- /// This method follows the behavior of .
- ///
- public static string GetExtension(string path)
- {
- _ = path.Length;
- return GetExtension(path.AsSpan()).ToString();
- }
-
- ///
- /// Returns an extension (including the period ".") of the specified path string.
- ///
- /// The path string from which to get the extension.
- ///
- /// The extension of the specified path (including the period "."),
- /// or an empty string if no extension is present.
- ///
- ///
- /// returns an empty string ("")
- /// if the extension consists solely of a period (e.g., "file."), which differs from
- /// , which returns "." in this case.
- /// This method follows the behavior of .
- ///
- public static ReadOnlySpan GetExtension(ReadOnlySpan path)
- {
- for (var i = path.Length - 1; i >= 0; i--)
- {
- if (path[i] == '.')
- {
- if (i == path.Length - 1)
- break;
-
- return path.Slice(i);
- }
-
- if (path[i] == '/' || path[i] == '\\')
- break;
- }
-
- return default;
- }
-
- ///
- /// Returns the file name and extension for the specified path.
- ///
- /// The path from which to get the file name and extension.
- ///
- /// The file name and extension for the .
- ///
- public static string GetFileName(string path)
- {
- var length = path.Length;
-
- var fileName = GetFileName(path.AsSpan());
- if (fileName.Length != length)
- return fileName.ToString();
-
- return path;
- }
-
- ///
- /// Returns the file name and extension for the specified path.
- ///
- /// The path from which to get the file name and extension.
- ///
- /// The file name and extension for the .
- ///
- public static ReadOnlySpan GetFileName(ReadOnlySpan path)
- {
- var index = path.LastIndexOfAny('/', '\\');
-
- return index >= 0
- ? path.Slice(index + 1)
- : path;
- }
-
- ///
- /// Returns the directory portion for the specified path.
- ///
- /// The path to retrieve the directory portion from.
- ///
- /// The directory portion of , or an empty span if
- /// is empty or denotes a root directory.
- ///
- public static string GetDirectoryName(string path)
- {
- _ = path.Length;
-
- var offset = GetDirectoryNameOffset(path);
-
- if (offset > 0 && (uint)offset < (uint)path.Length)
- return path[..offset];
-
- if (offset == 0)
- return "/";
-
- return "";
- }
-
- ///
- /// Returns the directory portion for the specified path.
- ///
- /// The path to retrieve the directory portion from.
- ///
- /// The directory portion of , or an empty span if
- /// is empty or denotes a root directory.
- ///
- public static ReadOnlySpan GetDirectoryName(ReadOnlySpan path)
- {
- var offset = GetDirectoryNameOffset(path);
-
- if (offset > 0 && (uint)offset < (uint)path.Length)
- return path.Slice(0, offset);
-
- return offset == 0 ? "/" : "";
- }
-
- ///
- /// Determines if the specified path is in a normalized form.
- ///
- /// The path to test.
- ///
- /// if the path is in a normalized form;
- /// otherwise, .
- ///
- public static bool IsNormalized(ReadOnlySpan path)
- {
- if (path is ['/', ..])
- {
- var prior = path[0];
-
- for (var j = 1; (uint)j < (uint)path.Length; j++)
- {
- var ch = path[j];
- if (ch == '\\' || (ch == '/' && prior == '/'))
- return false;
-
- if (ch == '.' && prior == '/')
- {
- if ((uint)j + 1 >= (uint)path.Length)
- return false;
-
- if (path[j + 1] is '/' or '\\')
- return false;
-
- if (path[j + 1] == '.')
- {
- if ((uint)j + 2 >= (uint)path.Length)
- return false;
-
- if (path[j + 2] is '/' or '\\')
- return false;
- }
- }
-
- prior = ch;
- }
-
- if (prior != '/' || path.Length == 1)
- return true;
- }
-
- return false;
- }
-
- ///
- /// Normalizes the specified path by resolving relative segments and applying formatting.
- ///
- /// The file or directory path to normalize.
- ///
- /// The fully normalized and absolute form of .
- ///
- ///
- /// The normalization process includes the following steps:
- ///
- /// Resolves relative segments (e.g., ".", "..").
- /// Removes consecutive slashes.
- /// Replaces backslashes with forward slashes.
- /// Ensures the path starts with a leading slash.
- /// Removes any trailing slash.
- ///
- ///
- public static string Normalize(string path)
- {
- //
- // Short-circuit optimization:
- // Many paths are already normalized except for a missing leading slash.
- // It's more efficient to first check/add the leading slash
- // before performing full normalization.
- //
-
- if (!path.StartsWith('/'))
- path = $"/{path}";
-
- if (IsNormalized(path))
- return path;
-
- char[]? rented = null;
-
- var buffer = path.Length + 1 <= StackallocThreshold
- ? stackalloc char[StackallocThreshold]
- : rented = ArrayPool.Shared.Rent(path.Length + 1);
-
- var index = 0;
-
- foreach (var s in PathTokenizer.Tokenize(path))
- {
- if (s.Length == 0 || s is ['.'])
- continue;
-
- if (s is ['.', '.'])
- {
- // Unwind back to the last separator
- index = buffer[..index].LastIndexOf('/');
-
- // If no separator is found, reset to start
- // (mimics Path.GetFullPath behavior)
- if (index < 0)
- index = 0;
- }
- else
- {
- buffer[index] = '/';
- s.CopyTo(buffer.Slice(index + 1));
- index += s.Length + 1;
- }
- }
-
- var result = index != 0
- ? buffer[..index].ToString()
- : "/";
-
- if (rented is not null)
- ArrayPool.Shared.Return(rented);
-
- return result;
- }
-
- ///
- /// Concatenates two paths into a single path.
- ///
- /// The path to join.
- /// The path to join.
- ///
- /// The concatenated path.
- ///
- public static string Join(string path1, string path2)
- {
- if (path1.Length == 0)
- return path2;
-
- if (path2.Length == 0)
- return path1;
-
- if (HasTrailingSlash(path1) || HasLeadingSlash(path2))
- return string.Concat(path1, path2);
-
- return string.Concat(path1, "/", path2);
- }
-
- ///
- /// Concatenates two paths into a single path.
- ///
- /// The path to join.
- /// The path to join.
- ///
- /// The concatenated path.
- ///
- public static string Join(ReadOnlySpan path1, ReadOnlySpan path2)
- {
- if (path1.Length == 0)
- return path2.ToString();
-
- if (path2.Length == 0)
- return path1.ToString();
-
- if (HasTrailingSlash(path1) || HasLeadingSlash(path2))
- return string.Concat(path1, path2);
-
- return string.Concat(path1, "/", path2);
- }
-
- ///
- /// Determines whether the specified path string starts with a directory separator.
- ///
- /// The path to test.
- ///
- /// if the path has a leading directory separator;
- /// otherwise, .
- ///
- public static bool HasLeadingSlash(string path) =>
- path.StartsWith('/') || path.StartsWith('\\');
-
- ///
- /// Determines whether the specified path string ends in a directory separator.
- ///
- /// The path to test.
- ///
- /// if the path has a trailing directory separator;
- /// otherwise, .
- ///
- public static bool HasTrailingSlash(string path) =>
- path.EndsWith('/') || path.EndsWith('\\');
-
- ///
- /// Determines whether the specified path string starts with a directory separator.
- ///
- /// The path to test.
- ///
- /// if the path has a leading directory separator;
- /// otherwise, .
- ///
- public static bool HasLeadingSlash(ReadOnlySpan path)
- {
- if (path.Length != 0)
- {
- var ch = path[0];
- if (ch == '/' || ch == '\\')
- return true;
- }
-
- return false;
- }
-
- ///
- /// Determines whether the specified path string ends in a directory separator.
- ///
- /// The path to test.
- ///
- /// if the path has a trailing directory separator;
- /// otherwise, .
- ///
- public static bool HasTrailingSlash(ReadOnlySpan path)
- {
- if (path.Length != 0)
- {
- var ch = path[^1];
- if (ch == '/' || ch == '\\')
- return true;
- }
-
- return false;
- }
-
- private static int GetDirectoryNameOffset(ReadOnlySpan path)
- {
- var lastIndex = path.LastIndexOfAny('/', '\\');
- var index = lastIndex;
-
- // Process consecutive separators
- while ((uint)index - 1 < (uint)path.Length && (path[index - 1] == '/' || path[index - 1] == '\\'))
- index--;
-
- // Case where the path consists of separators only
- if (index == 0 && lastIndex + 1 == path.Length)
- index = -1;
-
- return index;
- }
-}
+using System.Buffers;
+
+using Ramstack.FileSystem.Utilities;
+
+namespace Ramstack.FileSystem;
+
+///
+/// Provides utility methods for working with virtual paths.
+///
+///
+///
+/// For compatibility across different implementations of
+/// and operating systems, directory separators are unified to use both
+/// backslashes and forward slashes ("/" and "\").
+///
+///
+/// This approach will be reviewed once a better solution is found.
+///
+///
+/// When normalizing paths by using the method ,
+/// backslashes ("\") will be replaced with forward slashes ("/") forcibly.
+///
+///
+public static class VirtualPath
+{
+ ///
+ /// The threshold size in characters for using stack allocation.
+ ///
+ private const int StackallocThreshold = 256;
+
+ ///
+ /// Returns an extension (including the period ".") of the specified path string.
+ ///
+ /// The path string from which to get the extension.
+ ///
+ /// The extension of the specified path (including the period "."),
+ /// or an empty string if no extension is present.
+ ///
+ ///
+ /// returns an empty string ("")
+ /// if the extension consists solely of a period (e.g., "file."), which differs from
+ /// , which returns "." in this case.
+ /// This method follows the behavior of .
+ ///
+ public static string GetExtension(string path)
+ {
+ _ = path.Length;
+ return GetExtension(path.AsSpan()).ToString();
+ }
+
+ ///
+ /// Returns an extension (including the period ".") of the specified path string.
+ ///
+ /// The path string from which to get the extension.
+ ///
+ /// The extension of the specified path (including the period "."),
+ /// or an empty string if no extension is present.
+ ///
+ ///
+ /// returns an empty string ("")
+ /// if the extension consists solely of a period (e.g., "file."), which differs from
+ /// , which returns "." in this case.
+ /// This method follows the behavior of .
+ ///
+ public static ReadOnlySpan GetExtension(ReadOnlySpan path)
+ {
+ for (var i = path.Length - 1; i >= 0; i--)
+ {
+ if (path[i] == '.')
+ {
+ if (i == path.Length - 1)
+ break;
+
+ return path.Slice(i);
+ }
+
+ if (path[i] == '/' || path[i] == '\\')
+ break;
+ }
+
+ return default;
+ }
+
+ ///
+ /// Returns the file name and extension for the specified path.
+ ///
+ /// The path from which to get the file name and extension.
+ ///
+ /// The file name and extension for the .
+ ///
+ public static string GetFileName(string path)
+ {
+ var length = path.Length;
+
+ var fileName = GetFileName(path.AsSpan());
+ if (fileName.Length != length)
+ return fileName.ToString();
+
+ return path;
+ }
+
+ ///
+ /// Returns the file name and extension for the specified path.
+ ///
+ /// The path from which to get the file name and extension.
+ ///
+ /// The file name and extension for the .
+ ///
+ public static ReadOnlySpan GetFileName(ReadOnlySpan path)
+ {
+ var index = path.LastIndexOfAny('/', '\\');
+
+ return index >= 0
+ ? path.Slice(index + 1)
+ : path;
+ }
+
+ ///
+ /// Returns the directory portion for the specified path.
+ ///
+ /// The path to retrieve the directory portion from.
+ ///
+ /// The directory portion of , or an empty span if
+ /// is empty or denotes a root directory.
+ ///
+ public static string GetDirectoryName(string path)
+ {
+ _ = path.Length;
+
+ var offset = GetDirectoryNameOffset(path);
+
+ if (offset > 0 && (uint)offset < (uint)path.Length)
+ return path[..offset];
+
+ if (offset == 0)
+ return "/";
+
+ return "";
+ }
+
+ ///
+ /// Returns the directory portion for the specified path.
+ ///
+ /// The path to retrieve the directory portion from.
+ ///
+ /// The directory portion of , or an empty span if
+ /// is empty or denotes a root directory.
+ ///
+ public static ReadOnlySpan GetDirectoryName(ReadOnlySpan path)
+ {
+ var offset = GetDirectoryNameOffset(path);
+
+ if (offset > 0 && (uint)offset < (uint)path.Length)
+ return path.Slice(0, offset);
+
+ return offset == 0 ? "/" : "";
+ }
+
+ ///
+ /// Determines if the specified path is in a normalized form.
+ ///
+ /// The path to test.
+ ///
+ /// if the path is in a normalized form;
+ /// otherwise, .
+ ///
+ public static bool IsNormalized(ReadOnlySpan path)
+ {
+ if (path is ['/', ..])
+ {
+ var prior = path[0];
+
+ for (var j = 1; (uint)j < (uint)path.Length; j++)
+ {
+ var ch = path[j];
+ if (ch == '\\' || (ch == '/' && prior == '/'))
+ return false;
+
+ if (ch == '.' && prior == '/')
+ {
+ if ((uint)j + 1 >= (uint)path.Length)
+ return false;
+
+ if (path[j + 1] is '/' or '\\')
+ return false;
+
+ if (path[j + 1] == '.')
+ {
+ if ((uint)j + 2 >= (uint)path.Length)
+ return false;
+
+ if (path[j + 2] is '/' or '\\')
+ return false;
+ }
+ }
+
+ prior = ch;
+ }
+
+ if (prior != '/' || path.Length == 1)
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Normalizes the specified path by resolving relative segments and applying formatting.
+ ///
+ /// The file or directory path to normalize.
+ ///
+ /// The fully normalized and absolute form of .
+ ///
+ ///
+ /// The normalization process includes the following steps:
+ ///
+ /// Resolves relative segments (e.g., ".", "..").
+ /// Removes consecutive slashes.
+ /// Replaces backslashes with forward slashes.
+ /// Ensures the path starts with a leading slash.
+ /// Removes any trailing slash.
+ ///
+ ///
+ public static string Normalize(string path)
+ {
+ //
+ // Short-circuit optimization:
+ // Many paths are already normalized except for a missing leading slash.
+ // It's more efficient to first check/add the leading slash
+ // before performing full normalization.
+ //
+
+ if (!path.StartsWith('/'))
+ path = $"/{path}";
+
+ if (IsNormalized(path))
+ return path;
+
+ char[]? rented = null;
+
+ var buffer = path.Length + 1 <= StackallocThreshold
+ ? stackalloc char[StackallocThreshold]
+ : rented = ArrayPool.Shared.Rent(path.Length + 1);
+
+ var index = 0;
+
+ foreach (var s in PathTokenizer.Tokenize(path))
+ {
+ if (s.Length == 0 || s is ['.'])
+ continue;
+
+ if (s is ['.', '.'])
+ {
+ // Unwind back to the last separator
+ index = buffer[..index].LastIndexOf('/');
+
+ // If no separator is found, reset to start
+ // (mimics Path.GetFullPath behavior)
+ if (index < 0)
+ index = 0;
+ }
+ else
+ {
+ buffer[index] = '/';
+ s.CopyTo(buffer.Slice(index + 1));
+ index += s.Length + 1;
+ }
+ }
+
+ var result = index != 0
+ ? buffer[..index].ToString()
+ : "/";
+
+ if (rented is not null)
+ ArrayPool.Shared.Return(rented);
+
+ return result;
+ }
+
+ ///
+ /// Concatenates two paths into a single path.
+ ///
+ /// The path to join.
+ /// The path to join.
+ ///
+ /// The concatenated path.
+ ///
+ public static string Join(string path1, string path2)
+ {
+ if (path1.Length == 0)
+ return path2;
+
+ if (path2.Length == 0)
+ return path1;
+
+ if (HasTrailingSlash(path1) || HasLeadingSlash(path2))
+ return string.Concat(path1, path2);
+
+ return string.Concat(path1, "/", path2);
+ }
+
+ ///
+ /// Concatenates two paths into a single path.
+ ///
+ /// The path to join.
+ /// The path to join.
+ ///
+ /// The concatenated path.
+ ///
+ public static string Join(ReadOnlySpan path1, ReadOnlySpan path2)
+ {
+ if (path1.Length == 0)
+ return path2.ToString();
+
+ if (path2.Length == 0)
+ return path1.ToString();
+
+ if (HasTrailingSlash(path1) || HasLeadingSlash(path2))
+ return string.Concat(path1, path2);
+
+ return string.Concat(path1, "/", path2);
+ }
+
+ ///
+ /// Determines whether the specified path string starts with a directory separator.
+ ///
+ /// The path to test.
+ ///
+ /// if the path has a leading directory separator;
+ /// otherwise, .
+ ///
+ public static bool HasLeadingSlash(string path) =>
+ path.StartsWith('/') || path.StartsWith('\\');
+
+ ///
+ /// Determines whether the specified path string ends in a directory separator.
+ ///
+ /// The path to test.
+ ///
+ /// if the path has a trailing directory separator;
+ /// otherwise, .
+ ///
+ public static bool HasTrailingSlash(string path) =>
+ path.EndsWith('/') || path.EndsWith('\\');
+
+ ///
+ /// Determines whether the specified path string starts with a directory separator.
+ ///
+ /// The path to test.
+ ///
+ /// if the path has a leading directory separator;
+ /// otherwise, .
+ ///
+ public static bool HasLeadingSlash(ReadOnlySpan path)
+ {
+ if (path.Length != 0)
+ {
+ var ch = path[0];
+ if (ch == '/' || ch == '\\')
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Determines whether the specified path string ends in a directory separator.
+ ///
+ /// The path to test.
+ ///
+ /// if the path has a trailing directory separator;
+ /// otherwise, .
+ ///
+ public static bool HasTrailingSlash(ReadOnlySpan path)
+ {
+ if (path.Length != 0)
+ {
+ var ch = path[^1];
+ if (ch == '/' || ch == '\\')
+ return true;
+ }
+
+ return false;
+ }
+
+ private static int GetDirectoryNameOffset(ReadOnlySpan path)
+ {
+ var lastIndex = path.LastIndexOfAny('/', '\\');
+ var index = lastIndex;
+
+ // Process consecutive separators
+ while ((uint)index - 1 < (uint)path.Length && (path[index - 1] == '/' || path[index - 1] == '\\'))
+ index--;
+
+ // Case where the path consists of separators only
+ if (index == 0 && lastIndex + 1 == path.Length)
+ index = -1;
+
+ return index;
+ }
+}
diff --git a/src/Ramstack.FileSystem.Adapters/README.md b/src/Ramstack.FileSystem.Adapters/README.md
index 9032aa6..013d4a7 100644
--- a/src/Ramstack.FileSystem.Adapters/README.md
+++ b/src/Ramstack.FileSystem.Adapters/README.md
@@ -1,61 +1,61 @@
-# Ramstack.FileSystem.Adapters
-[](https://nuget.org/packages/Ramstack.FileSystem.Adapters)
-[](https://github.com/rameel/ramstack.filesystem/blob/main/LICENSE)
-
-Provides an implementation of `Ramstack.FileSystem` that integrates with `Microsoft.Extensions.FileProviders`.
-
-## Getting Started
-
-To install the `Ramstack.FileSystem.Adapters` [NuGet package](https://www.nuget.org/packages/Ramstack.FileSystem.Adapters)
-in your project, run the following command:
-```console
-dotnet add package Ramstack.FileSystem.Adapters
-```
-## Usage
-
-```csharp
-using Microsoft.Extensions.FileProviders;
-using Microsoft.Extensions.FileProviders.Physical;
-using Ramstack.FileSystem.Adapters;
-
-// Create a physical file provider that points to the specified directory
-IFileProvider provider = new PhysicalFileProvider(@"C:\path");
-
-// Create a virtual file system adapter that wraps the physical file provider.
-//
-// An added benefit is that now information about the entire file hierarchy is available
-// for files and directories provided by the underlying provider.
-IVirtualFileSystem fs = new VirtualFileSystemAdapter(provider);
-
-await foreach (VirtualFile file in fs.GetFilesAsync("/"))
-{
- Console.WriteLine(file.FullName);
-}
-```
-
-## Related Projects
-- [Ramstack.FileSystem.Abstractions](https://www.nuget.org/packages/Ramstack.FileSystem.Abstractions) - Provides a virtual file system abstraction.
-- [Ramstack.FileSystem.Physical](https://www.nuget.org/packages/Ramstack.FileSystem.Physical) - Provides an implementation based on the local file system.
-- [Ramstack.FileSystem.Azure](https://www.nuget.org/packages/Ramstack.FileSystem.Azure) - Provides an implementation using Azure Blob Storage.
-- [Ramstack.FileSystem.Amazon](https://www.nuget.org/packages/Ramstack.FileSystem.Amazon) - Provides an implementation using Amazon S3 storage.
-- [Ramstack.FileSystem.Google](https://www.nuget.org/packages/Ramstack.FileSystem.Google) - Provides an implementation using Google Cloud Storage.
-- [Ramstack.FileSystem.Readonly](https://www.nuget.org/packages/Ramstack.FileSystem.Readonly) - Provides a read-only wrapper for the underlying file system.
-- [Ramstack.FileSystem.Globbing](https://www.nuget.org/packages/Ramstack.FileSystem.Globbing) - Wraps the file system, filtering files and directories using glob patterns.
-- [Ramstack.FileSystem.Prefixed](https://www.nuget.org/packages/Ramstack.FileSystem.Prefixed) - Adds a prefix to file paths within the underlying file system.
-- [Ramstack.FileSystem.Sub](https://www.nuget.org/packages/Ramstack.FileSystem.Sub) - Wraps the underlying file system, restricting access to a specific subpath.
-- [Ramstack.FileSystem.Composite](https://www.nuget.org/packages/Ramstack.FileSystem.Composite) - Provides an implementation that combines multiple file systems into a single composite file system.
-
-## Supported Versions
-
-| | Version |
-|------|----------------|
-| .NET | 6, 7, 8, 9, 10 |
-
-## Contributions
-
-Bug reports and contributions are welcome.
-
-## License
-
-This package is released as open source under the **MIT License**.
-See the [LICENSE](https://github.com/rameel/ramstack.virtualfiles/blob/main/LICENSE) file for more details.
+# Ramstack.FileSystem.Adapters
+[](https://nuget.org/packages/Ramstack.FileSystem.Adapters)
+[](https://github.com/rameel/ramstack.filesystem/blob/main/LICENSE)
+
+Provides an implementation of `Ramstack.FileSystem` that integrates with `Microsoft.Extensions.FileProviders`.
+
+## Getting Started
+
+To install the `Ramstack.FileSystem.Adapters` [NuGet package](https://www.nuget.org/packages/Ramstack.FileSystem.Adapters)
+in your project, run the following command:
+```console
+dotnet add package Ramstack.FileSystem.Adapters
+```
+## Usage
+
+```csharp
+using Microsoft.Extensions.FileProviders;
+using Microsoft.Extensions.FileProviders.Physical;
+using Ramstack.FileSystem.Adapters;
+
+// Create a physical file provider that points to the specified directory
+IFileProvider provider = new PhysicalFileProvider(@"C:\path");
+
+// Create a virtual file system adapter that wraps the physical file provider.
+//
+// An added benefit is that now information about the entire file hierarchy is available
+// for files and directories provided by the underlying provider.
+IVirtualFileSystem fs = new VirtualFileSystemAdapter(provider);
+
+await foreach (VirtualFile file in fs.GetFilesAsync("/"))
+{
+ Console.WriteLine(file.FullName);
+}
+```
+
+## Related Projects
+- [Ramstack.FileSystem.Abstractions](https://www.nuget.org/packages/Ramstack.FileSystem.Abstractions) - Provides a virtual file system abstraction.
+- [Ramstack.FileSystem.Physical](https://www.nuget.org/packages/Ramstack.FileSystem.Physical) - Provides an implementation based on the local file system.
+- [Ramstack.FileSystem.Azure](https://www.nuget.org/packages/Ramstack.FileSystem.Azure) - Provides an implementation using Azure Blob Storage.
+- [Ramstack.FileSystem.Amazon](https://www.nuget.org/packages/Ramstack.FileSystem.Amazon) - Provides an implementation using Amazon S3 storage.
+- [Ramstack.FileSystem.Google](https://www.nuget.org/packages/Ramstack.FileSystem.Google) - Provides an implementation using Google Cloud Storage.
+- [Ramstack.FileSystem.Readonly](https://www.nuget.org/packages/Ramstack.FileSystem.Readonly) - Provides a read-only wrapper for the underlying file system.
+- [Ramstack.FileSystem.Globbing](https://www.nuget.org/packages/Ramstack.FileSystem.Globbing) - Wraps the file system, filtering files and directories using glob patterns.
+- [Ramstack.FileSystem.Prefixed](https://www.nuget.org/packages/Ramstack.FileSystem.Prefixed) - Adds a prefix to file paths within the underlying file system.
+- [Ramstack.FileSystem.Sub](https://www.nuget.org/packages/Ramstack.FileSystem.Sub) - Wraps the underlying file system, restricting access to a specific subpath.
+- [Ramstack.FileSystem.Composite](https://www.nuget.org/packages/Ramstack.FileSystem.Composite) - Provides an implementation that combines multiple file systems into a single composite file system.
+
+## Supported Versions
+
+| | Version |
+|------|----------------|
+| .NET | 6, 7, 8, 9, 10 |
+
+## Contributions
+
+Bug reports and contributions are welcome.
+
+## License
+
+This package is released as open source under the **MIT License**.
+See the [LICENSE](https://github.com/rameel/ramstack.virtualfiles/blob/main/LICENSE) file for more details.
diff --git a/src/Ramstack.FileSystem.Adapters/Ramstack.FileSystem.Adapters.csproj b/src/Ramstack.FileSystem.Adapters/Ramstack.FileSystem.Adapters.csproj
index 89d8e6d..0934aae 100644
--- a/src/Ramstack.FileSystem.Adapters/Ramstack.FileSystem.Adapters.csproj
+++ b/src/Ramstack.FileSystem.Adapters/Ramstack.FileSystem.Adapters.csproj
@@ -1,77 +1,77 @@
-
-
-
- net6.0;net8.0
- Provides adapters for integrating Ramstack.FileSystem with Microsoft.Extensions.FileProviders.
- enable
- enable
- preview
- true
- true
-
-
-
- 649;1591
- CS8600;CS8601;CS8602;CS8603;CS8604;CS8610;CS8613;CS8614;CS8618;CS8619;CS8620;CS8625;CS8643;CS8653;CS8714
-
-
-
- https://github.com/rameel/ramstack.filesystem
- Rameel
- filesystem;fileprovider
- https://github.com/rameel/ramstack.filesystem#readme
- README.md
- MIT
- true
- snupkg
- true
- true
- true
- true
-
-
-
- true
-
-
-
-
- Properties\AssemblyInfo.cs
-
-
- Utilities\EnumerableExtensions.cs
-
-
-
-
-
-
-
-
-
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
-
-
-
-
- Properties\README.md
- True
- \
-
-
-
-
+
+
+
+ net6.0;net8.0
+ Provides adapters for integrating Ramstack.FileSystem with Microsoft.Extensions.FileProviders.
+ enable
+ enable
+ preview
+ true
+ true
+
+
+
+ 649;1591
+ CS8600;CS8601;CS8602;CS8603;CS8604;CS8610;CS8613;CS8614;CS8618;CS8619;CS8620;CS8625;CS8643;CS8653;CS8714
+
+
+
+ https://github.com/rameel/ramstack.filesystem
+ Rameel
+ filesystem;fileprovider
+ https://github.com/rameel/ramstack.filesystem#readme
+ README.md
+ MIT
+ true
+ snupkg
+ true
+ true
+ true
+ true
+
+
+
+ true
+
+
+
+
+ Properties\AssemblyInfo.cs
+
+
+ Utilities\EnumerableExtensions.cs
+
+
+
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+ Properties\README.md
+ True
+ \
+
+
+
+
diff --git a/src/Ramstack.FileSystem.Adapters/VirtualDirectoryAdapter.cs b/src/Ramstack.FileSystem.Adapters/VirtualDirectoryAdapter.cs
index 672610c..daee4e2 100644
--- a/src/Ramstack.FileSystem.Adapters/VirtualDirectoryAdapter.cs
+++ b/src/Ramstack.FileSystem.Adapters/VirtualDirectoryAdapter.cs
@@ -1,118 +1,118 @@
-using Microsoft.Extensions.FileProviders;
-
-using Ramstack.FileSystem.Utilities;
-
-namespace Ramstack.FileSystem.Adapters;
-
-///
-/// Represents an adapter for the instance.
-///
-internal sealed class VirtualDirectoryAdapter : VirtualDirectory
-{
- private readonly VirtualFileSystemAdapter _fs;
- private IFileInfo? _file;
- private IDirectoryContents? _directory;
-
- ///
- public override IVirtualFileSystem FileSystem => _fs;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The file system associated with this directory.
- /// The path of the directory.
- /// The associated with this directory.
- public VirtualDirectoryAdapter(VirtualFileSystemAdapter fileSystem, string path, IFileInfo file) : base(path) =>
- // ReSharper disable once SuspiciousTypeConversion.Global
- (_fs, _file, _directory) = (fileSystem, file, file as IDirectoryContents);
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The file system associated with this directory.
- /// The path of the directory.
- /// The associated with this directory.
- public VirtualDirectoryAdapter(VirtualFileSystemAdapter fileSystem, string path, IDirectoryContents directory) : base(path) =>
- (_fs, _directory) = (fileSystem, directory);
-
- ///
- protected override void RefreshCore()
- {
- _file = null;
- _directory = _fs.FileProvider.GetDirectoryContents(FullName);
- }
-
- ///
- protected override ValueTask GetPropertiesCoreAsync(CancellationToken cancellationToken)
- {
- var properties = _file?.Exists ?? _directory!.Exists
- ? VirtualNodeProperties.None
- : VirtualNodeProperties.Unavailable;
-
- return new ValueTask(properties);
- }
-
- ///
- protected override ValueTask ExistsCoreAsync(CancellationToken cancellationToken)
- {
- var exists = _file?.Exists ?? _directory!.Exists;
- return new ValueTask(exists);
- }
-
- ///