Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions CliWrap.Tests/BufferingSpecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,39 @@ public async Task I_can_execute_a_command_with_buffering_and_get_the_stdout_and_
result.StandardError.Trim().Should().Be("Hello stdout and stderr");
}

[Fact(Timeout = 15000)]
public async Task I_can_execute_a_command_with_buffering_and_use_an_implicit_conversion_to_get_stdout()
{
// Arrange
var cmd = Cli.Wrap(Dummy.Program.FilePath)
.WithArguments(["echo", "Hello stdout", "--target", "stdout"]);

// Act
var result = await cmd.ExecuteBufferedAsync();

// Assert
((string)result)
.Trim()
.Should()
.Be("Hello stdout");
}

[Fact(Timeout = 15000)]
public async Task I_can_execute_a_command_with_buffering_and_use_deconstruction_to_get_stdout_and_stderr()
{
// Arrange
var cmd = Cli.Wrap(Dummy.Program.FilePath)
.WithArguments(["echo", "Hello stdout and stderr", "--target", "all"]);

// Act
var (exitCode, stdOut, stdErr) = await cmd.ExecuteBufferedAsync();

// Assert
exitCode.Should().Be(0);
stdOut.Trim().Should().Be("Hello stdout and stderr");
stdErr.Trim().Should().Be("Hello stdout and stderr");
}

[Fact(Timeout = 15000)]
public async Task I_can_execute_a_command_with_buffering_and_not_hang_on_large_stdout_and_stderr()
{
Expand Down
16 changes: 16 additions & 0 deletions CliWrap.Tests/ExecutionSpecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,22 @@ public async Task I_can_execute_a_command_and_get_the_exit_code_and_execution_ti
result.RunTime.Should().BeGreaterThan(TimeSpan.Zero);
}

[Fact(Timeout = 15000)]
public async Task I_can_execute_a_command_and_use_an_implicit_conversion_to_get_the_exit_code()
{
// Arrange
var cmd = Cli.Wrap(Dummy.Program.FilePath);

// Act
var result = await cmd.ExecuteAsync();

// Assert
((int)result)
.Should()
.Be(0);
((bool)result).Should().BeTrue();
}

[Fact(Timeout = 15000)]
public async Task I_can_execute_a_command_and_get_the_associated_process_ID()
{
Expand Down
4 changes: 1 addition & 3 deletions CliWrap/Buffered/BufferedCommandExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ namespace CliWrap.Buffered;
/// </summary>
public static class BufferedCommandExtensions
{
/// <summary>
/// Buffered execution model.
/// </summary>
/// <inheritdoc cref="BufferedCommandExtensions" />
extension(Command command)
{
/// <summary>
Expand Down
20 changes: 19 additions & 1 deletion CliWrap/Buffered/BufferedCommandResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace CliWrap.Buffered;
/// <summary>
/// Result of a command execution, with buffered text data from standard output and standard error streams.
/// </summary>
public class BufferedCommandResult(
public partial class BufferedCommandResult(
int exitCode,
DateTimeOffset startTime,
DateTimeOffset exitTime,
Expand All @@ -22,4 +22,22 @@ string standardError
/// Standard error data produced by the underlying process.
/// </summary>
public string StandardError { get; } = standardError;

/// <summary>
/// Deconstructs the result into its most important components.
/// </summary>
public void Deconstruct(out int exitCode, out string standardOutput, out string standardError)
{
exitCode = ExitCode;
standardOutput = StandardOutput;
standardError = StandardError;
}
}

public partial class BufferedCommandResult
{
/// <summary>
/// Converts the result to a string value that corresponds to the <see cref="BufferedCommandResult.StandardOutput" /> property.
/// </summary>
public static implicit operator string(BufferedCommandResult result) => result.StandardOutput;
}
15 changes: 14 additions & 1 deletion CliWrap/CommandResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace CliWrap;
/// <summary>
/// Result of a command execution.
/// </summary>
public class CommandResult(int exitCode, DateTimeOffset startTime, DateTimeOffset exitTime)
public partial class CommandResult(int exitCode, DateTimeOffset startTime, DateTimeOffset exitTime)
{
/// <summary>
/// Exit code set by the underlying process.
Expand All @@ -32,3 +32,16 @@ public class CommandResult(int exitCode, DateTimeOffset startTime, DateTimeOffse
/// </summary>
public TimeSpan RunTime => ExitTime - StartTime;
}

public partial class CommandResult
{
/// <summary>
/// Converts the result to an integer value that corresponds to the <see cref="CommandResult.ExitCode" /> property.
/// </summary>
public static implicit operator int(CommandResult result) => result.ExitCode;

/// <summary>
/// Converts the result to a boolean value that corresponds to the <see cref="CommandResult.IsSuccess" /> property.
/// </summary>
public static implicit operator bool(CommandResult result) => result.IsSuccess;
}
4 changes: 1 addition & 3 deletions CliWrap/EventStream/PullEventStreamCommandExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ namespace CliWrap.EventStream;
// TODO: (breaking change) split the partial class into two separate classes, one for each execution model
public static partial class EventStreamCommandExtensions
{
/// <summary>
/// Event stream execution model.
/// </summary>
/// <inheritdoc cref="EventStreamCommandExtensions" />
extension(Command command)
{
/// <summary>
Expand Down
4 changes: 1 addition & 3 deletions CliWrap/EventStream/PushEventStreamCommandExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ namespace CliWrap.EventStream;
// TODO: (breaking change) split the partial class into two separate classes, one for each execution model
public static partial class EventStreamCommandExtensions
{
/// <summary>
/// Event stream execution model.
/// </summary>
/// <inheritdoc cref="EventStreamCommandExtensions" />
extension(Command command)
{
/// <summary>
Expand Down
44 changes: 34 additions & 10 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ var result = await Cli.Wrap("path/to/exe")
.ExecuteAsync();

// Result contains:
// -- result.IsSuccess (bool)
// -- result.ExitCode (int)
// -- result.IsSuccess (bool)
// -- result.StartTime (DateTimeOffset)
// -- result.ExitTime (DateTimeOffset)
// -- result.RunTime (TimeSpan)
Expand Down Expand Up @@ -133,10 +133,10 @@ var result = await Cli.Wrap("path/to/exe")
.ExecuteBufferedAsync();

// Result contains:
// -- result.IsSuccess (bool)
// -- result.StandardOutput (string)
// -- result.StandardError (string)
// -- result.StandardOutput (string) *
// -- result.StandardError (string) *
// -- result.ExitCode (int)
// -- result.IsSuccess (bool)
// -- result.StartTime (DateTimeOffset)
// -- result.ExitTime (DateTimeOffset)
// -- result.RunTime (TimeSpan)
Expand Down Expand Up @@ -565,9 +565,31 @@ var result = await Cli.Wrap("foo")
.WithArguments(["bar"])
.ExecuteBufferedAsync();

var exitCode = result.ExitCode;
var stdOut = result.StandardOutput;
var stdErr = result.StandardError;
// Result contains:
// -- result.StandardOutput (string) *
// -- result.StandardError (string) *
// -- result.ExitCode (int)
// -- result.IsSuccess (bool)
// -- result.StartTime (DateTimeOffset)
// -- result.ExitTime (DateTimeOffset)
// -- result.RunTime (TimeSpan)
```

The result object returned by this execution model also has a few convenience shorthands.
For example, you can use the tuple deconstruction syntax to extract the exit code, standard output, and standard error — all within a single statement:

```csharp
var (exitCode, stdOut, stdErr) = await Cli.Wrap("foo")
.WithArguments(["bar"])
.ExecuteBufferedAsync();
```

If you are only interested in the standard output part of the result, you can streamline the code even further by using the provided implicit conversion:

```csharp
string stdOut = await Cli.Wrap("foo")
.WithArguments(["bar"])
.ExecuteBufferedAsync();
```

By default, `ExecuteBufferedAsync()` assumes that the underlying process uses the default encoding (`Encoding.Default`) for writing text to the console.
Expand Down Expand Up @@ -776,10 +798,12 @@ public async Task GitPushAsync(CancellationToken cancellationToken = default)
> **Note**:
> Similarly to `ExecuteAsync()`, cancellation is also supported by `ExecuteBufferedAsync()`, `ListenAsync()`, and `Observe()`.

### Retrieving process-related information
### Process information

The task returned by `ExecuteAsync()` and `ExecuteBufferedAsync()` is, in fact, not a regular `Task<T>`, but an instance of `CommandTask<T>`.
This is a specialized awaitable object that contains additional information about the process associated with the executing command:
This is a specialized awaitable object that contains additional information about the process associated with the executing command.

If you separate the task into its own variable, you can use the `ProcessId` property to access the ID of the underlying process:

```csharp
var task = Cli.Wrap("foo")
Expand All @@ -791,4 +815,4 @@ var processId = task.ProcessId;

// Wait for the task to complete
await task;
```
```