feat: add overloads that allow to the the buffer size for the copy
All checks were successful
default / dotnet-default-workflow (pull_request) Successful in 1m18s
default / dotnet-default-workflow (push) Successful in 1m14s

This commit is contained in:
Louis Seubert 2026-08-25 20:04:13 +02:00
commit cce841a06e
3 changed files with 152 additions and 28 deletions

View file

@ -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()
{

View file

@ -67,6 +67,14 @@ public abstract partial class PipeSource
return Create(stream.CopyToAsync);
}
/// <summary>
/// Creates a pipe source that reads from the specified stream.
/// </summary>
public static PipeSource FromStream(Stream stream, int bufferSize)
{
return Create((target, token) => stream.CopyToAsync(target, bufferSize, token));
}
/// <summary>
/// Creates a pipe source that reads from the specified file.
/// </summary>
@ -79,6 +87,18 @@ public abstract partial class PipeSource
});
}
/// <summary>
/// Creates a pipe source that reads from the specified file.
/// </summary>
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);
});
}
/// <summary>
/// Creates a pipe source that reads from the specified memory buffer.
/// </summary>
@ -89,11 +109,12 @@ public abstract partial class PipeSource
}
/// <summary>
/// Creates a pipe source that reads from the specified byte array.
/// Creates a pipe source that reads from the specified string.
/// Uses <see cref="Console.InputEncoding" /> for encoding.
/// </summary>
public static PipeSource FromBytes(byte[] data)
public static PipeSource FromString(string str)
{
return FromBytes((ReadOnlyMemory<byte>)data);
return FromString(str, Console.InputEncoding);
}
/// <summary>
@ -104,15 +125,6 @@ public abstract partial class PipeSource
return FromBytes(encoding.GetBytes(str));
}
/// <summary>
/// Creates a pipe source that reads from the specified string.
/// Uses <see cref="Console.InputEncoding" /> for encoding.
/// </summary>
public static PipeSource FromString(string str)
{
return FromString(str, Console.InputEncoding);
}
/// <summary>
/// Creates a pipe source that reads from the standard output of the specified command.
/// </summary>

View file

@ -155,6 +155,17 @@ public partial class PipeTarget
await origin.CopyToAsync(stream, cancellationToken));
}
/// <summary>
/// Creates a pipe target that writes to the specified stream.
/// </summary>
/// <param name="stream">The stream to which the contents of the source will be copied.</param>
/// <param name="bufferSize">The size, in bytes, of the buffer. This value must be greater than zero. The default size is 81920.</param>
public static PipeTarget ToStream(Stream stream, int bufferSize)
{
return Create(async (origin, cancellationToken) =>
await origin.CopyToAsync(stream, bufferSize, cancellationToken));
}
/// <summary>
/// Creates a pipe target that writes to the specified file.
/// </summary>
@ -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);
});
}
/// <summary>
/// Creates a pipe target that writes to the specified file.
/// </summary>
/// <param name="filePath">The path and name of the file to create and write the content to.</param>
/// <param name="bufferSize">The size, in bytes, of the buffer. This value must be greater than zero. The default size is 81920.</param>
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);
});
}
/// <summary>
/// Creates a pipe target that writes to the specified string builder.
/// Uses <see cref="Console.OutputEncoding" /> for decoding.
/// </summary>
public static PipeTarget ToStringBuilder(StringBuilder stringBuilder)
{
return ToStringBuilder(stringBuilder, Console.OutputEncoding);
}
/// <summary>
/// Creates a pipe target that writes to the specified string builder.
/// </summary>
@ -191,12 +239,12 @@ public partial class PipeTarget
}
/// <summary>
/// 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 <see cref="Console.OutputEncoding" /> for decoding.
/// </summary>
public static PipeTarget ToStringBuilder(StringBuilder stringBuilder)
public static PipeTarget ToDelegate(Func<string, CancellationToken, Task> func)
{
return ToStringBuilder(stringBuilder, Console.OutputEncoding);
return ToDelegate(func, Console.OutputEncoding);
}
/// <summary>
@ -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 <see cref="Console.OutputEncoding" /> for decoding.
/// </summary>
public static PipeTarget ToDelegate(Func<string, CancellationToken, Task> func)
public static PipeTarget ToDelegate(Func<string, Task> func)
{
return ToDelegate(func, Console.OutputEncoding);
}
@ -232,12 +280,12 @@ public partial class PipeTarget
}
/// <summary>
/// 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 <see cref="Console.OutputEncoding" /> for decoding.
/// </summary>
public static PipeTarget ToDelegate(Func<string, Task> func)
public static PipeTarget ToDelegate(Action<string> action)
{
return ToDelegate(func, Console.OutputEncoding);
return ToDelegate(action, Console.OutputEncoding);
}
/// <summary>
@ -253,15 +301,6 @@ public partial class PipeTarget
}, encoding);
}
/// <summary>
/// Creates a pipe target that invokes the specified synchronous delegate on every line written to the stream.
/// Uses <see cref="Console.OutputEncoding" /> for decoding.
/// </summary>
public static PipeTarget ToDelegate(Action<string> action)
{
return ToDelegate(action, Console.OutputEncoding);
}
/// <summary>
/// Creates a pipe target that replicates data over multiple inner targets.
/// </summary>