Skip to content

Commit

Permalink
minor refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
adamralph committed Dec 25, 2023
1 parent a30fc36 commit 4eccdef
Show file tree
Hide file tree
Showing 17 changed files with 1,120 additions and 1,143 deletions.
1 change: 1 addition & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ csharp_style_expression_bodied_local_functions = true
csharp_style_expression_bodied_methods = true
csharp_style_expression_bodied_operators = true
csharp_style_expression_bodied_properties = true
csharp_style_namespace_declarations = file_scoped
csharp_style_var_elsewhere = true
csharp_style_var_for_built_in_types = true
csharp_style_var_when_type_is_apparent = true
Expand Down
845 changes: 420 additions & 425 deletions SimpleExec/Command.cs

Large diffs are not rendered by default.

55 changes: 27 additions & 28 deletions SimpleExec/ExitCodeException.cs
Original file line number Diff line number Diff line change
@@ -1,41 +1,40 @@
using System;

namespace SimpleExec
{
namespace SimpleExec;

#if NET8_0_OR_GREATER
/// <summary>
/// The command exited with an unexpected exit code.
/// </summary>
/// <param name="exitCode">The exit code of the command.</param>
/// <summary>
/// The command exited with an unexpected exit code.
/// </summary>
/// <param name="exitCode">The exit code of the command.</param>
#pragma warning disable CA1032 // Implement standard exception constructors
public class ExitCodeException(int exitCode) : Exception
public class ExitCodeException(int exitCode) : Exception
#pragma warning restore CA1032 // Implement standard exception constructors
{
/// <summary>
/// Gets the exit code of the command.
/// </summary>
public int ExitCode { get; } = exitCode;
#else
{
/// <summary>
/// The command exited with an unexpected exit code.
/// Gets the exit code of the command.
/// </summary>
public int ExitCode { get; } = exitCode;
#else
/// <summary>
/// The command exited with an unexpected exit code.
/// </summary>
#pragma warning disable CA1032 // Implement standard exception constructors
public class ExitCodeException : Exception
public class ExitCodeException : Exception
#pragma warning restore CA1032 // Implement standard exception constructors
{
/// <summary>
/// Constructs an instance of a <see cref="ExitCodeException"/>.
/// </summary>
/// <param name="exitCode">The exit code of the command.</param>
public ExitCodeException(int exitCode) => this.ExitCode = exitCode;
{
/// <summary>
/// Constructs an instance of a <see cref="ExitCodeException"/>.
/// </summary>
/// <param name="exitCode">The exit code of the command.</param>
public ExitCodeException(int exitCode) => this.ExitCode = exitCode;

/// <summary>
/// Gets the exit code of the command.
/// </summary>
public int ExitCode { get; }
/// <summary>
/// Gets the exit code of the command.
/// </summary>
public int ExitCode { get; }
#endif

/// <inheritdoc/>
public override string Message => $"The command exited with code {this.ExitCode}.";
}
/// <inheritdoc/>
public override string Message => $"The command exited with code {this.ExitCode}.";
}
53 changes: 26 additions & 27 deletions SimpleExec/ExitCodeReadException.cs
Original file line number Diff line number Diff line change
@@ -1,36 +1,35 @@
using System;

namespace SimpleExec
{
/// <summary>
/// The command being read exited with an unexpected exit code.
/// </summary>
namespace SimpleExec;

/// <summary>
/// The command being read exited with an unexpected exit code.
/// </summary>
#pragma warning disable CA1032 // Implement standard exception constructors
public class ExitCodeReadException : ExitCodeException
public class ExitCodeReadException : ExitCodeException
#pragma warning restore CA1032 // Implement standard exception constructors
{
private static readonly string twoNewLines = $"{Environment.NewLine}{Environment.NewLine}";
{
private static readonly string twoNewLines = $"{Environment.NewLine}{Environment.NewLine}";

/// <summary>
/// Constructs an instance of a <see cref="ExitCodeReadException"/>.
/// </summary>
/// <param name="exitCode">The exit code of the command.</param>
/// <param name="standardOutput">The contents of standard output (stdout).</param>
/// <param name="standardError">The contents of standard error (stderr).</param>
public ExitCodeReadException(int exitCode, string standardOutput, string standardError) : base(exitCode) => (this.StandardOutput, this.StandardError) = (standardOutput, standardError);
/// <summary>
/// Constructs an instance of a <see cref="ExitCodeReadException"/>.
/// </summary>
/// <param name="exitCode">The exit code of the command.</param>
/// <param name="standardOutput">The contents of standard output (stdout).</param>
/// <param name="standardError">The contents of standard error (stderr).</param>
public ExitCodeReadException(int exitCode, string standardOutput, string standardError) : base(exitCode) => (this.StandardOutput, this.StandardError) = (standardOutput, standardError);

/// <summary>
/// Gets the contents of standard output (stdout).
/// </summary>
public string StandardOutput { get; }
/// <summary>
/// Gets the contents of standard output (stdout).
/// </summary>
public string StandardOutput { get; }

/// <summary>
/// Gets the contents of standard error (stderr).
/// </summary>
public string StandardError { get; }
/// <summary>
/// Gets the contents of standard error (stderr).
/// </summary>
public string StandardError { get; }

/// <inheritdoc/>
public override string Message =>
$"{base.Message}{twoNewLines}Standard output (stdout):{twoNewLines}{this.StandardOutput}{twoNewLines}Standard error (stderr):{twoNewLines}{this.StandardError}";
}
/// <inheritdoc/>
public override string Message =>
$"{base.Message}{twoNewLines}Standard output (stdout):{twoNewLines}{this.StandardOutput}{twoNewLines}Standard error (stderr):{twoNewLines}{this.StandardError}";
}
201 changes: 100 additions & 101 deletions SimpleExec/ProcessExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,146 +5,145 @@
using System.Threading;
using System.Threading.Tasks;

namespace SimpleExec
namespace SimpleExec;

internal static class ProcessExtensions
{
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, bool noEcho, string echoPrefix, bool cancellationIgnoresProcessTree, CancellationToken cancellationToken)
var cancelled = 0L;

if (!noEcho)
{
var cancelled = 0L;
Console.Out.Write(process.StartInfo.GetEchoLines(echoPrefix));
}

if (!noEcho)
_ = process.Start();

using var register = cancellationToken.Register(
() =>
{
Console.Out.Write(process.StartInfo.GetEchoLines(echoPrefix));
}
if (process.TryKill(cancellationIgnoresProcessTree))
{
_ = Interlocked.Increment(ref cancelled);
}
},
useSynchronizationContext: false);

_ = process.Start();
process.WaitForExit();

using var register = cancellationToken.Register(
if (Interlocked.Read(ref cancelled) == 1)
{
cancellationToken.ThrowIfCancellationRequested();
}
}

public static async Task RunAsync(this Process process, bool noEcho, string echoPrefix, bool cancellationIgnoresProcessTree, CancellationToken cancellationToken)
{
using var sync = new SemaphoreSlim(1, 1);
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);

process.EnableRaisingEvents = true;
process.Exited += (_, _) => sync.Run(() => tcs.Task.Status != TaskStatus.Canceled, () => _ = tcs.TrySetResult());

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

_ = process.Start();

await using var register = cancellationToken.Register(
() => sync.Run(
() => tcs.Task.Status != TaskStatus.RanToCompletion,
() =>
{
if (process.TryKill(cancellationIgnoresProcessTree))
{
_ = Interlocked.Increment(ref cancelled);
_ = tcs.TrySetCanceled(cancellationToken);
}
},
useSynchronizationContext: false);
}),
useSynchronizationContext: false).ConfigureAwait(false);

process.WaitForExit();
await tcs.Task.ConfigureAwait(false);
}

if (Interlocked.Read(ref cancelled) == 1)
{
cancellationToken.ThrowIfCancellationRequested();
}
}
private static string GetEchoLines(this System.Diagnostics.ProcessStartInfo info, string echoPrefix)
{
var builder = new StringBuilder();

public static async Task RunAsync(this Process process, bool noEcho, string echoPrefix, bool cancellationIgnoresProcessTree, CancellationToken cancellationToken)
if (!string.IsNullOrEmpty(info.WorkingDirectory))
{
using var sync = new SemaphoreSlim(1, 1);
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
_ = builder.AppendLine(CultureInfo.InvariantCulture, $"{echoPrefix}: Working directory: {info.WorkingDirectory}");
}

process.EnableRaisingEvents = true;
process.Exited += (_, _) => sync.Run(() => tcs.Task.Status != TaskStatus.Canceled, () => _ = tcs.TrySetResult());
if (info.ArgumentList.Count > 0)
{
_ = builder.AppendLine(CultureInfo.InvariantCulture, $"{echoPrefix}: {info.FileName}");

if (!noEcho)
foreach (var arg in info.ArgumentList)
{
await Console.Out.WriteAsync(process.StartInfo.GetEchoLines(echoPrefix)).ConfigureAwait(false);
_ = builder.AppendLine(CultureInfo.InvariantCulture, $"{echoPrefix}: {arg}");
}

_ = process.Start();

await using var register = cancellationToken.Register(
() => sync.Run(
() => tcs.Task.Status != TaskStatus.RanToCompletion,
() =>
{
if (process.TryKill(cancellationIgnoresProcessTree))
{
_ = tcs.TrySetCanceled(cancellationToken);
}
}),
useSynchronizationContext: false).ConfigureAwait(false);

await tcs.Task.ConfigureAwait(false);
}

private static string GetEchoLines(this System.Diagnostics.ProcessStartInfo info, string echoPrefix)
else
{
var builder = new StringBuilder();
_ = builder.AppendLine(CultureInfo.InvariantCulture, $"{echoPrefix}: {info.FileName}{(string.IsNullOrEmpty(info.Arguments) ? "" : $" {info.Arguments}")}");
}

if (!string.IsNullOrEmpty(info.WorkingDirectory))
{
_ = builder.AppendLine(CultureInfo.InvariantCulture, $"{echoPrefix}: Working directory: {info.WorkingDirectory}");
}
return builder.ToString();
}

if (info.ArgumentList.Count > 0)
{
_ = builder.AppendLine(CultureInfo.InvariantCulture, $"{echoPrefix}: {info.FileName}");
private static bool TryKill(this Process process, bool ignoreProcessTree)
{
// exceptions may be thrown for all kinds of reasons
// and the _same exception_ may be thrown for all kinds of reasons
// System.Diagnostics.Process is "fine"
try
{
process.Kill(!ignoreProcessTree);
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception)
#pragma warning restore CA1031 // Do not catch general exception types
{
return false;
}

foreach (var arg in info.ArgumentList)
{
_ = builder.AppendLine(CultureInfo.InvariantCulture, $"{echoPrefix}: {arg}");
}
}
else
{
_ = builder.AppendLine(CultureInfo.InvariantCulture, $"{echoPrefix}: {info.FileName}{(string.IsNullOrEmpty(info.Arguments) ? "" : $" {info.Arguments}")}");
}
return true;
}

return builder.ToString();
private static void Run(this SemaphoreSlim sync, Func<bool> doubleCheckPredicate, Action action)
{
if (!doubleCheckPredicate())
{
return;
}

private static bool TryKill(this Process process, bool ignoreProcessTree)
try
{
// exceptions may be thrown for all kinds of reasons
// and the _same exception_ may be thrown for all kinds of reasons
// System.Diagnostics.Process is "fine"
try
{
process.Kill(!ignoreProcessTree);
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception)
#pragma warning restore CA1031 // Do not catch general exception types
{
return false;
}

return true;
sync.Wait();
}
catch (ObjectDisposedException)
{
return;
}

private static void Run(this SemaphoreSlim sync, Func<bool> doubleCheckPredicate, Action action)
try
{
if (!doubleCheckPredicate())
if (doubleCheckPredicate())
{
return;
action();
}

}
finally
{
try
{
sync.Wait();
_ = sync.Release();
}
catch (ObjectDisposedException)
{
return;
}

try
{
if (doubleCheckPredicate())
{
action();
}
}
finally
{
try
{
_ = sync.Release();
}
catch (ObjectDisposedException)
{
}
}
}
}
Expand Down
Loading

0 comments on commit 4eccdef

Please sign in to comment.