From cce841a06e72c09a954a233afe076506bc4ea058 Mon Sep 17 00:00:00 2001 From: Louis Seubert Date: Tue, 25 Aug 2026 20:04:13 +0200 Subject: [PATCH] feat: add overloads that allow to the the buffer size for the copy --- src/process.tests/PipingTests.cs | 73 ++++++++++++++++++++++++++++++++ src/process/PipeSource.cs | 36 ++++++++++------ src/process/PipeTarget.cs | 73 ++++++++++++++++++++++++-------- 3 files changed, 153 insertions(+), 29 deletions(-) diff --git a/src/process.tests/PipingTests.cs b/src/process.tests/PipingTests.cs index a184743..82f23b3 100644 --- a/src/process.tests/PipingTests.cs +++ b/src/process.tests/PipingTests.cs @@ -64,6 +64,42 @@ internal sealed class PipingTests await Assert.That(result.StandardOutput.Trim()).IsEqualTo("Hello World!"); } + [Test] + public async Task I_can_execute_a_command_and_pipe_the_stdin_from_a_stream_with_a_custom_buffer_size() + { + // Arrange + using var source = new MemoryStream("Hello World!"u8.ToArray()); + + var cmd = PipeSource.FromStream(source, 1) | + new Command(Testing.Fixture.Program.FilePath) + .WithArguments("echo-stdin"); + + // Act + var result = await cmd.ExecuteBufferedAsync(); + + // Assert + await Assert.That(result.StandardOutput.Trim()).IsEqualTo("Hello World!"); + } + + [Test] + public async Task I_can_execute_a_command_and_pipe_the_stdin_from_a_file_with_a_custom_buffer_size() + { + // Arrange + using var dir = TestTempDirectory.Create(); + var filePath = Path.Combine(dir.Path, "input.txt"); + await File.WriteAllTextAsync(filePath, "Hello World!"); + + var cmd = PipeSource.FromFile(filePath, 1) | + new Command(Testing.Fixture.Program.FilePath) + .WithArguments("echo-stdin"); + + // Act + var result = await cmd.ExecuteBufferedAsync(); + + // Assert + await Assert.That(result.StandardOutput.Trim()).IsEqualTo("Hello World!"); + } + [Test] public async Task I_can_execute_a_command_and_pipe_the_stdin_from_memory() { @@ -220,6 +256,43 @@ internal sealed class PipingTests await Assert.That(stream.Length).IsEqualTo(100_000); } + [Test] + public async Task I_can_execute_a_command_and_pipe_the_stdout_into_a_stream_with_a_custom_buffer_size() + { + // Arrange + using var stream = new MemoryStream(); + var target = PipeTarget.ToStream(stream, 1); + + var cmd = new Command(Testing.Fixture.Program.FilePath) + .WithArguments(["generate", "blob", "--length", "100000"]) | + target; + + // Act + await cmd.ExecuteAsync(); + + // Assert + await Assert.That(stream.Length).IsEqualTo(100_000); + } + + [Test] + public async Task I_can_execute_a_command_and_pipe_the_stdout_into_a_file_with_a_custom_buffer_size() + { + // Arrange + using var dir = TestTempDirectory.Create(); + var filePath = Path.Combine(dir.Path, "output.bin"); + var target = PipeTarget.ToFile(filePath, 1); + + var cmd = new Command(Testing.Fixture.Program.FilePath) + .WithArguments(["generate", "blob", "--length", "100000"]) | + target; + + // Act + await cmd.ExecuteAsync(); + + // Assert + await Assert.That(new FileInfo(filePath).Length).IsEqualTo(100_000); + } + [Test] public async Task I_can_execute_a_command_and_pipe_the_stdout_into_a_string_builder() { diff --git a/src/process/PipeSource.cs b/src/process/PipeSource.cs index ed221c5..4f00157 100644 --- a/src/process/PipeSource.cs +++ b/src/process/PipeSource.cs @@ -67,6 +67,14 @@ public abstract partial class PipeSource return Create(stream.CopyToAsync); } + /// + /// Creates a pipe source that reads from the specified stream. + /// + public static PipeSource FromStream(Stream stream, int bufferSize) + { + return Create((target, token) => stream.CopyToAsync(target, bufferSize, token)); + } + /// /// Creates a pipe source that reads from the specified file. /// @@ -79,6 +87,18 @@ public abstract partial class PipeSource }); } + /// + /// Creates a pipe source that reads from the specified file. + /// + public static PipeSource FromFile(string filePath, int bufferSize) + { + return Create(async (destination, cancellationToken) => + { + await using var source = File.OpenRead(filePath); + await source.CopyToAsync(destination, bufferSize, cancellationToken); + }); + } + /// /// Creates a pipe source that reads from the specified memory buffer. /// @@ -89,11 +109,12 @@ public abstract partial class PipeSource } /// - /// Creates a pipe source that reads from the specified byte array. + /// Creates a pipe source that reads from the specified string. + /// Uses for encoding. /// - public static PipeSource FromBytes(byte[] data) + public static PipeSource FromString(string str) { - return FromBytes((ReadOnlyMemory)data); + return FromString(str, Console.InputEncoding); } /// @@ -104,15 +125,6 @@ public abstract partial class PipeSource return FromBytes(encoding.GetBytes(str)); } - /// - /// Creates a pipe source that reads from the specified string. - /// Uses for encoding. - /// - public static PipeSource FromString(string str) - { - return FromString(str, Console.InputEncoding); - } - /// /// Creates a pipe source that reads from the standard output of the specified command. /// diff --git a/src/process/PipeTarget.cs b/src/process/PipeTarget.cs index f9cc99f..fbc1c97 100644 --- a/src/process/PipeTarget.cs +++ b/src/process/PipeTarget.cs @@ -155,6 +155,17 @@ public partial class PipeTarget await origin.CopyToAsync(stream, cancellationToken)); } + /// + /// Creates a pipe target that writes to the specified stream. + /// + /// The stream to which the contents of the source will be copied. + /// The size, in bytes, of the buffer. This value must be greater than zero. The default size is 81920. + public static PipeTarget ToStream(Stream stream, int bufferSize) + { + return Create(async (origin, cancellationToken) => + await origin.CopyToAsync(stream, bufferSize, cancellationToken)); + } + /// /// Creates a pipe target that writes to the specified file. /// @@ -162,11 +173,48 @@ public partial class PipeTarget { return Create(async (origin, cancellationToken) => { - await using var target = File.Create(filePath); + var options = new FileStreamOptions + { + Access = FileAccess.Write, + Mode = FileMode.Create, + Share = FileShare.Read, + Options = FileOptions.Asynchronous + }; + await using var target = new FileStream(filePath, options); await origin.CopyToAsync(target, cancellationToken); }); } + /// + /// Creates a pipe target that writes to the specified file. + /// + /// The path and name of the file to create and write the content to. + /// The size, in bytes, of the buffer. This value must be greater than zero. The default size is 81920. + public static PipeTarget ToFile(string filePath, int bufferSize) + { + return Create(async (origin, cancellationToken) => + { + var options = new FileStreamOptions + { + Access = FileAccess.Write, + Mode = FileMode.Create, + Share = FileShare.Read, + Options = FileOptions.Asynchronous + }; + await using var target = new FileStream(filePath, options); + await origin.CopyToAsync(target, bufferSize, cancellationToken); + }); + } + + /// + /// Creates a pipe target that writes to the specified string builder. + /// Uses for decoding. + /// + public static PipeTarget ToStringBuilder(StringBuilder stringBuilder) + { + return ToStringBuilder(stringBuilder, Console.OutputEncoding); + } + /// /// Creates a pipe target that writes to the specified string builder. /// @@ -191,12 +239,12 @@ public partial class PipeTarget } /// - /// Creates a pipe target that writes to the specified string builder. + /// Creates a pipe target that invokes the specified asynchronous delegate on every line written to the stream. /// Uses for decoding. /// - public static PipeTarget ToStringBuilder(StringBuilder stringBuilder) + public static PipeTarget ToDelegate(Func func) { - return ToStringBuilder(stringBuilder, Console.OutputEncoding); + return ToDelegate(func, Console.OutputEncoding); } /// @@ -218,7 +266,7 @@ public partial class PipeTarget /// Creates a pipe target that invokes the specified asynchronous delegate on every line written to the stream. /// Uses for decoding. /// - public static PipeTarget ToDelegate(Func func) + public static PipeTarget ToDelegate(Func func) { return ToDelegate(func, Console.OutputEncoding); } @@ -232,12 +280,12 @@ public partial class PipeTarget } /// - /// Creates a pipe target that invokes the specified asynchronous delegate on every line written to the stream. + /// Creates a pipe target that invokes the specified synchronous delegate on every line written to the stream. /// Uses for decoding. /// - public static PipeTarget ToDelegate(Func func) + public static PipeTarget ToDelegate(Action action) { - return ToDelegate(func, Console.OutputEncoding); + return ToDelegate(action, Console.OutputEncoding); } /// @@ -253,15 +301,6 @@ public partial class PipeTarget }, encoding); } - /// - /// Creates a pipe target that invokes the specified synchronous delegate on every line written to the stream. - /// Uses for decoding. - /// - public static PipeTarget ToDelegate(Action action) - { - return ToDelegate(action, Console.OutputEncoding); - } - /// /// Creates a pipe target that replicates data over multiple inner targets. ///