Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ var (standardOutput3, standardError3) = await ReadAsync("foo", new[] { "arg1", "

```c#
string workingDirectory = "",
IEnumerable<string> secrets = null,
bool noEcho = false,
string? echoPrefix = null,
Action<IDictionary<string, string?>>? configureEnvironment = null,
Expand Down
24 changes: 17 additions & 7 deletions SimpleExec/Command.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public static class Command
/// <param name="name">The name of the command. This can be a path to an executable file.</param>
/// <param name="args">The arguments to pass to the command.</param>
/// <param name="workingDirectory">The working directory in which to run the command.</param>
/// <param name="secrets">A list of secrets that are redacted by replacement with "***" when echoing the resulting command line and the working directory (if specified) to standard output (stdout).</param>
/// <param name="noEcho">Whether to echo the resulting command line and working directory (if specified) to standard output (stdout).</param>
/// <param name="echoPrefix">The prefix to use when echoing the command line and working directory (if specified) to standard output (stdout).</param>
/// <param name="configureEnvironment">An action which configures environment variables for the command.</param>
Expand All @@ -44,6 +45,7 @@ public static void Run(
string name,
string args = "",
string workingDirectory = "",
IEnumerable<string>? secrets = null,
bool noEcho = false,
string? echoPrefix = null,
Action<IDictionary<string, string?>>? configureEnvironment = null,
Expand All @@ -60,7 +62,7 @@ public static void Run(
false,
configureEnvironment ?? DefaultAction,
createNoWindow)
.Run(noEcho, echoPrefix ?? DefaultEchoPrefix, handleExitCode, cancellationIgnoresProcessTree, cancellationToken);
.Run(secrets ?? [], noEcho, echoPrefix ?? DefaultEchoPrefix, handleExitCode, cancellationIgnoresProcessTree, cancellationToken);

/// <summary>
/// Runs a command without redirecting standard output (stdout) and standard error (stderr) and without writing to standard input (stdin).
Expand All @@ -72,6 +74,7 @@ public static void Run(
/// As with <see cref="System.Diagnostics.ProcessStartInfo.ArgumentList"/>, the strings don't need to be escaped.
/// </param>
/// <param name="workingDirectory">The working directory in which to run the command.</param>
/// <param name="secrets">A list of secrets that are redacted by replacement with "***" when echoing the resulting command line and the working directory (if specified) to standard output (stdout).</param>
/// <param name="noEcho">Whether to echo the resulting command name, arguments, and working directory (if specified) to standard output (stdout).</param>
/// <param name="echoPrefix">The prefix to use when echoing the command name, arguments, and working directory (if specified) to standard output (stdout).</param>
/// <param name="configureEnvironment">An action which configures environment variables for the command.</param>
Expand All @@ -92,6 +95,7 @@ public static void Run(
string name,
IEnumerable<string> args,
string workingDirectory = "",
IEnumerable<string>? secrets = null,
bool noEcho = false,
string? echoPrefix = null,
Action<IDictionary<string, string?>>? configureEnvironment = null,
Expand All @@ -108,10 +112,11 @@ public static void Run(
false,
configureEnvironment ?? DefaultAction,
createNoWindow)
.Run(noEcho, echoPrefix ?? DefaultEchoPrefix, handleExitCode, cancellationIgnoresProcessTree, cancellationToken);
.Run(secrets ?? [], noEcho, echoPrefix ?? DefaultEchoPrefix, handleExitCode, cancellationIgnoresProcessTree, cancellationToken);

private static void Run(
this System.Diagnostics.ProcessStartInfo startInfo,
IEnumerable<string> secrets,
bool noEcho,
string echoPrefix,
Func<int, bool>? handleExitCode,
Expand All @@ -121,7 +126,7 @@ private static void Run(
using var process = new Process();
process.StartInfo = startInfo;

process.Run(noEcho, echoPrefix, cancellationIgnoresProcessTree, cancellationToken);
process.Run(secrets, noEcho, echoPrefix, cancellationIgnoresProcessTree, cancellationToken);

if (!(handleExitCode?.Invoke(process.ExitCode) ?? false) && process.ExitCode != 0)
{
Expand All @@ -136,6 +141,7 @@ private static void Run(
/// <param name="name">The name of the command. This can be a path to an executable file.</param>
/// <param name="args">The arguments to pass to the command.</param>
/// <param name="workingDirectory">The working directory in which to run the command.</param>
/// <param name="secrets">A list of secrets that are redacted by replacement with "***" when echoing the resulting command line and the working directory (if specified) to standard output (stdout).</param>
/// <param name="noEcho">Whether to echo the resulting command line and working directory (if specified) to standard output (stdout).</param>
/// <param name="echoPrefix">The prefix to use when echoing the command line and working directory (if specified) to standard output (stdout).</param>
/// <param name="configureEnvironment">An action which configures environment variables for the command.</param>
Expand All @@ -161,6 +167,7 @@ public static Task RunAsync(
string name,
string args = "",
string workingDirectory = "",
IEnumerable<string>? secrets = null,
bool noEcho = false,
string? echoPrefix = null,
Action<IDictionary<string, string?>>? configureEnvironment = null,
Expand All @@ -177,7 +184,7 @@ public static Task RunAsync(
false,
configureEnvironment ?? DefaultAction,
createNoWindow)
.RunAsync(noEcho, echoPrefix ?? DefaultEchoPrefix, handleExitCode, cancellationIgnoresProcessTree, cancellationToken);
.RunAsync(secrets ?? [], noEcho, echoPrefix ?? DefaultEchoPrefix, handleExitCode, cancellationIgnoresProcessTree, cancellationToken);

/// <summary>
/// Runs a command asynchronously without redirecting standard output (stdout) and standard error (stderr) and without writing to standard input (stdin).
Expand All @@ -189,6 +196,7 @@ public static Task RunAsync(
/// As with <see cref="System.Diagnostics.ProcessStartInfo.ArgumentList"/>, the strings don't need to be escaped.
/// </param>
/// <param name="workingDirectory">The working directory in which to run the command.</param>
/// <param name="secrets">A list of secrets that are redacted by replacement with "***" when echoing the resulting command line and the working directory (if specified) to standard output (stdout).</param>
/// <param name="noEcho">Whether to echo the resulting command name, arguments, and working directory (if specified) to standard output (stdout).</param>
/// <param name="echoPrefix">The prefix to use when echoing the command name, arguments, and working directory (if specified) to standard output (stdout).</param>
/// <param name="configureEnvironment">An action which configures environment variables for the command.</param>
Expand All @@ -210,6 +218,7 @@ public static Task RunAsync(
string name,
IEnumerable<string> args,
string workingDirectory = "",
IEnumerable<string>? secrets = null,
bool noEcho = false,
string? echoPrefix = null,
Action<IDictionary<string, string?>>? configureEnvironment = null,
Expand All @@ -226,10 +235,11 @@ public static Task RunAsync(
false,
configureEnvironment ?? DefaultAction,
createNoWindow)
.RunAsync(noEcho, echoPrefix ?? DefaultEchoPrefix, handleExitCode, cancellationIgnoresProcessTree, cancellationToken);
.RunAsync(secrets ?? [], noEcho, echoPrefix ?? DefaultEchoPrefix, handleExitCode, cancellationIgnoresProcessTree, cancellationToken);

private static async Task RunAsync(
this System.Diagnostics.ProcessStartInfo startInfo,
IEnumerable<string> secrets,
bool noEcho,
string echoPrefix,
Func<int, bool>? handleExitCode,
Expand All @@ -239,7 +249,7 @@ private static async Task RunAsync(
using var process = new Process();
process.StartInfo = startInfo;

await process.RunAsync(noEcho, echoPrefix, cancellationIgnoresProcessTree, cancellationToken).ConfigureAwait(false);
await process.RunAsync(secrets, noEcho, echoPrefix, cancellationIgnoresProcessTree, cancellationToken).ConfigureAwait(false);

if (!(handleExitCode?.Invoke(process.ExitCode) ?? false) && process.ExitCode != 0)
{
Expand Down Expand Up @@ -367,7 +377,7 @@ private static async Task RunAsync(
process.StartInfo = startInfo;

#pragma warning disable CA2025 // Do not pass 'IDisposable' instances into unawaited tasks
var runProcess = process.RunAsync(true, "", cancellationIgnoresProcessTree, cancellationToken);
var runProcess = process.RunAsync([], true, "", cancellationIgnoresProcessTree, cancellationToken);
#pragma warning restore CA2025

Task<string> readOutput;
Expand Down
15 changes: 9 additions & 6 deletions SimpleExec/ProcessExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ namespace SimpleExec;

internal static class ProcessExtensions
{
public static void Run(this Process process, bool noEcho, string echoPrefix, bool cancellationIgnoresProcessTree, CancellationToken cancellationToken)
public static void Run(this Process process, IEnumerable<string> secrets, bool noEcho, string echoPrefix, bool cancellationIgnoresProcessTree, CancellationToken cancellationToken)
{
var cancelled = new StrongBox<long>(0);

if (!noEcho)
{
Console.Out.Write(process.StartInfo.GetEchoLines(echoPrefix));
Console.Out.Write(process.StartInfo.GetEchoLines(secrets, echoPrefix));
}

_ = process.Start();
Expand All @@ -36,7 +36,7 @@ public static void Run(this Process process, bool noEcho, string echoPrefix, boo
}
}

public static async Task RunAsync(this Process process, bool noEcho, string echoPrefix, bool cancellationIgnoresProcessTree, CancellationToken cancellationToken)
public static async Task RunAsync(this Process process, IEnumerable<string> secrets, bool noEcho, string echoPrefix, bool cancellationIgnoresProcessTree, CancellationToken cancellationToken)
{
using var sync = new SemaphoreSlim(1, 1);
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
Expand All @@ -46,7 +46,7 @@ public static async Task RunAsync(this Process process, bool noEcho, string echo

if (!noEcho)
{
await Console.Out.WriteAsync(process.StartInfo.GetEchoLines(echoPrefix)).ConfigureAwait(false);
await Console.Out.WriteAsync(process.StartInfo.GetEchoLines(secrets, echoPrefix)).ConfigureAwait(false);
}

_ = process.Start();
Expand All @@ -66,7 +66,7 @@ public static async Task RunAsync(this Process process, bool noEcho, string echo
await tcs.Task.ConfigureAwait(false);
}

private static string GetEchoLines(this System.Diagnostics.ProcessStartInfo info, string echoPrefix)
private static string GetEchoLines(this System.Diagnostics.ProcessStartInfo info, IEnumerable<string> secrets, string echoPrefix)
{
var builder = new StringBuilder();

Expand All @@ -89,9 +89,12 @@ private static string GetEchoLines(this System.Diagnostics.ProcessStartInfo info
_ = builder.AppendLine(CultureInfo.InvariantCulture, $"{echoPrefix}: {info.FileName}{(string.IsNullOrEmpty(info.Arguments) ? "" : $" {info.Arguments}")}");
}

return builder.ToString();
return builder.ToString().Redact(secrets);
}

private static string Redact(this string value, IEnumerable<string> secrets) =>
secrets.Aggregate(value, (current, secret) => current.Replace(secret, "***", StringComparison.OrdinalIgnoreCase));

private static bool TryKill(this Process process, bool ignoreProcessTree)
{
// exceptions may be thrown for all kinds of reasons
Expand Down
8 changes: 4 additions & 4 deletions SimpleExec/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ SimpleExec.ExitCodeReadException.StandardError.get -> string!
SimpleExec.ExitCodeReadException.StandardOutput.get -> string!
static SimpleExec.Command.ReadAsync(string! name, string! args = "", string! workingDirectory = "", System.Action<System.Collections.Generic.IDictionary<string!, string?>!>? configureEnvironment = null, System.Text.Encoding? encoding = null, System.Func<int, bool>? handleExitCode = null, string? standardInput = null, bool cancellationIgnoresProcessTree = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<(string! StandardOutput, string! StandardError)>!
static SimpleExec.Command.ReadAsync(string! name, System.Collections.Generic.IEnumerable<string!>! args, string! workingDirectory = "", System.Action<System.Collections.Generic.IDictionary<string!, string?>!>? configureEnvironment = null, System.Text.Encoding? encoding = null, System.Func<int, bool>? handleExitCode = null, string? standardInput = null, bool cancellationIgnoresProcessTree = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<(string! StandardOutput, string! StandardError)>!
static SimpleExec.Command.Run(string! name, string! args = "", string! workingDirectory = "", bool noEcho = false, string? echoPrefix = null, System.Action<System.Collections.Generic.IDictionary<string!, string?>!>? configureEnvironment = null, bool createNoWindow = false, System.Func<int, bool>? handleExitCode = null, bool cancellationIgnoresProcessTree = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> void
static SimpleExec.Command.Run(string! name, System.Collections.Generic.IEnumerable<string!>! args, string! workingDirectory = "", bool noEcho = false, string? echoPrefix = null, System.Action<System.Collections.Generic.IDictionary<string!, string?>!>? configureEnvironment = null, bool createNoWindow = false, System.Func<int, bool>? handleExitCode = null, bool cancellationIgnoresProcessTree = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> void
static SimpleExec.Command.RunAsync(string! name, string! args = "", string! workingDirectory = "", bool noEcho = false, string? echoPrefix = null, System.Action<System.Collections.Generic.IDictionary<string!, string?>!>? configureEnvironment = null, bool createNoWindow = false, System.Func<int, bool>? handleExitCode = null, bool cancellationIgnoresProcessTree = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
static SimpleExec.Command.RunAsync(string! name, System.Collections.Generic.IEnumerable<string!>! args, string! workingDirectory = "", bool noEcho = false, string? echoPrefix = null, System.Action<System.Collections.Generic.IDictionary<string!, string?>!>? configureEnvironment = null, bool createNoWindow = false, System.Func<int, bool>? handleExitCode = null, bool cancellationIgnoresProcessTree = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
static SimpleExec.Command.Run(string! name, string! args = "", string! workingDirectory = "", System.Collections.Generic.IEnumerable<string!>? secrets = null, bool noEcho = false, string? echoPrefix = null, System.Action<System.Collections.Generic.IDictionary<string!, string?>!>? configureEnvironment = null, bool createNoWindow = false, System.Func<int, bool>? handleExitCode = null, bool cancellationIgnoresProcessTree = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> void
static SimpleExec.Command.Run(string! name, System.Collections.Generic.IEnumerable<string!>! args, string! workingDirectory = "", System.Collections.Generic.IEnumerable<string!>? secrets = null, bool noEcho = false, string? echoPrefix = null, System.Action<System.Collections.Generic.IDictionary<string!, string?>!>? configureEnvironment = null, bool createNoWindow = false, System.Func<int, bool>? handleExitCode = null, bool cancellationIgnoresProcessTree = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> void
static SimpleExec.Command.RunAsync(string! name, string! args = "", string! workingDirectory = "", System.Collections.Generic.IEnumerable<string!>? secrets = null, bool noEcho = false, string? echoPrefix = null, System.Action<System.Collections.Generic.IDictionary<string!, string?>!>? configureEnvironment = null, bool createNoWindow = false, System.Func<int, bool>? handleExitCode = null, bool cancellationIgnoresProcessTree = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
static SimpleExec.Command.RunAsync(string! name, System.Collections.Generic.IEnumerable<string!>! args, string! workingDirectory = "", System.Collections.Generic.IEnumerable<string!>? secrets = null, bool noEcho = false, string? echoPrefix = null, System.Action<System.Collections.Generic.IDictionary<string!, string?>!>? configureEnvironment = null, bool createNoWindow = false, System.Func<int, bool>? handleExitCode = null, bool cancellationIgnoresProcessTree = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
Loading
Loading