Skip to content

Commit 2c95bef

Browse files
authored
Merge pull request #604 from adamralph/refactor
minor refactoring
2 parents a30fc36 + 4eccdef commit 2c95bef

17 files changed

+1120
-1143
lines changed

.editorconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ csharp_style_expression_bodied_local_functions = true
1717
csharp_style_expression_bodied_methods = true
1818
csharp_style_expression_bodied_operators = true
1919
csharp_style_expression_bodied_properties = true
20+
csharp_style_namespace_declarations = file_scoped
2021
csharp_style_var_elsewhere = true
2122
csharp_style_var_for_built_in_types = true
2223
csharp_style_var_when_type_is_apparent = true

SimpleExec/Command.cs

Lines changed: 420 additions & 425 deletions
Large diffs are not rendered by default.

SimpleExec/ExitCodeException.cs

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,40 @@
11
using System;
22

3-
namespace SimpleExec
4-
{
3+
namespace SimpleExec;
4+
55
#if NET8_0_OR_GREATER
6-
/// <summary>
7-
/// The command exited with an unexpected exit code.
8-
/// </summary>
9-
/// <param name="exitCode">The exit code of the command.</param>
6+
/// <summary>
7+
/// The command exited with an unexpected exit code.
8+
/// </summary>
9+
/// <param name="exitCode">The exit code of the command.</param>
1010
#pragma warning disable CA1032 // Implement standard exception constructors
11-
public class ExitCodeException(int exitCode) : Exception
11+
public class ExitCodeException(int exitCode) : Exception
1212
#pragma warning restore CA1032 // Implement standard exception constructors
13-
{
14-
/// <summary>
15-
/// Gets the exit code of the command.
16-
/// </summary>
17-
public int ExitCode { get; } = exitCode;
18-
#else
13+
{
1914
/// <summary>
20-
/// The command exited with an unexpected exit code.
15+
/// Gets the exit code of the command.
2116
/// </summary>
17+
public int ExitCode { get; } = exitCode;
18+
#else
19+
/// <summary>
20+
/// The command exited with an unexpected exit code.
21+
/// </summary>
2222
#pragma warning disable CA1032 // Implement standard exception constructors
23-
public class ExitCodeException : Exception
23+
public class ExitCodeException : Exception
2424
#pragma warning restore CA1032 // Implement standard exception constructors
25-
{
26-
/// <summary>
27-
/// Constructs an instance of a <see cref="ExitCodeException"/>.
28-
/// </summary>
29-
/// <param name="exitCode">The exit code of the command.</param>
30-
public ExitCodeException(int exitCode) => this.ExitCode = exitCode;
25+
{
26+
/// <summary>
27+
/// Constructs an instance of a <see cref="ExitCodeException"/>.
28+
/// </summary>
29+
/// <param name="exitCode">The exit code of the command.</param>
30+
public ExitCodeException(int exitCode) => this.ExitCode = exitCode;
3131

32-
/// <summary>
33-
/// Gets the exit code of the command.
34-
/// </summary>
35-
public int ExitCode { get; }
32+
/// <summary>
33+
/// Gets the exit code of the command.
34+
/// </summary>
35+
public int ExitCode { get; }
3636
#endif
3737

38-
/// <inheritdoc/>
39-
public override string Message => $"The command exited with code {this.ExitCode}.";
40-
}
38+
/// <inheritdoc/>
39+
public override string Message => $"The command exited with code {this.ExitCode}.";
4140
}
Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,35 @@
11
using System;
22

3-
namespace SimpleExec
4-
{
5-
/// <summary>
6-
/// The command being read exited with an unexpected exit code.
7-
/// </summary>
3+
namespace SimpleExec;
4+
5+
/// <summary>
6+
/// The command being read exited with an unexpected exit code.
7+
/// </summary>
88
#pragma warning disable CA1032 // Implement standard exception constructors
9-
public class ExitCodeReadException : ExitCodeException
9+
public class ExitCodeReadException : ExitCodeException
1010
#pragma warning restore CA1032 // Implement standard exception constructors
11-
{
12-
private static readonly string twoNewLines = $"{Environment.NewLine}{Environment.NewLine}";
11+
{
12+
private static readonly string twoNewLines = $"{Environment.NewLine}{Environment.NewLine}";
1313

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

22-
/// <summary>
23-
/// Gets the contents of standard output (stdout).
24-
/// </summary>
25-
public string StandardOutput { get; }
22+
/// <summary>
23+
/// Gets the contents of standard output (stdout).
24+
/// </summary>
25+
public string StandardOutput { get; }
2626

27-
/// <summary>
28-
/// Gets the contents of standard error (stderr).
29-
/// </summary>
30-
public string StandardError { get; }
27+
/// <summary>
28+
/// Gets the contents of standard error (stderr).
29+
/// </summary>
30+
public string StandardError { get; }
3131

32-
/// <inheritdoc/>
33-
public override string Message =>
34-
$"{base.Message}{twoNewLines}Standard output (stdout):{twoNewLines}{this.StandardOutput}{twoNewLines}Standard error (stderr):{twoNewLines}{this.StandardError}";
35-
}
32+
/// <inheritdoc/>
33+
public override string Message =>
34+
$"{base.Message}{twoNewLines}Standard output (stdout):{twoNewLines}{this.StandardOutput}{twoNewLines}Standard error (stderr):{twoNewLines}{this.StandardError}";
3635
}

SimpleExec/ProcessExtensions.cs

Lines changed: 100 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -5,146 +5,145 @@
55
using System.Threading;
66
using System.Threading.Tasks;
77

8-
namespace SimpleExec
8+
namespace SimpleExec;
9+
10+
internal static class ProcessExtensions
911
{
10-
internal static class ProcessExtensions
12+
public static void Run(this Process process, bool noEcho, string echoPrefix, bool cancellationIgnoresProcessTree, CancellationToken cancellationToken)
1113
{
12-
public static void Run(this Process process, bool noEcho, string echoPrefix, bool cancellationIgnoresProcessTree, CancellationToken cancellationToken)
14+
var cancelled = 0L;
15+
16+
if (!noEcho)
1317
{
14-
var cancelled = 0L;
18+
Console.Out.Write(process.StartInfo.GetEchoLines(echoPrefix));
19+
}
1520

16-
if (!noEcho)
21+
_ = process.Start();
22+
23+
using var register = cancellationToken.Register(
24+
() =>
1725
{
18-
Console.Out.Write(process.StartInfo.GetEchoLines(echoPrefix));
19-
}
26+
if (process.TryKill(cancellationIgnoresProcessTree))
27+
{
28+
_ = Interlocked.Increment(ref cancelled);
29+
}
30+
},
31+
useSynchronizationContext: false);
2032

21-
_ = process.Start();
33+
process.WaitForExit();
2234

23-
using var register = cancellationToken.Register(
35+
if (Interlocked.Read(ref cancelled) == 1)
36+
{
37+
cancellationToken.ThrowIfCancellationRequested();
38+
}
39+
}
40+
41+
public static async Task RunAsync(this Process process, bool noEcho, string echoPrefix, bool cancellationIgnoresProcessTree, CancellationToken cancellationToken)
42+
{
43+
using var sync = new SemaphoreSlim(1, 1);
44+
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
45+
46+
process.EnableRaisingEvents = true;
47+
process.Exited += (_, _) => sync.Run(() => tcs.Task.Status != TaskStatus.Canceled, () => _ = tcs.TrySetResult());
48+
49+
if (!noEcho)
50+
{
51+
await Console.Out.WriteAsync(process.StartInfo.GetEchoLines(echoPrefix)).ConfigureAwait(false);
52+
}
53+
54+
_ = process.Start();
55+
56+
await using var register = cancellationToken.Register(
57+
() => sync.Run(
58+
() => tcs.Task.Status != TaskStatus.RanToCompletion,
2459
() =>
2560
{
2661
if (process.TryKill(cancellationIgnoresProcessTree))
2762
{
28-
_ = Interlocked.Increment(ref cancelled);
63+
_ = tcs.TrySetCanceled(cancellationToken);
2964
}
30-
},
31-
useSynchronizationContext: false);
65+
}),
66+
useSynchronizationContext: false).ConfigureAwait(false);
3267

33-
process.WaitForExit();
68+
await tcs.Task.ConfigureAwait(false);
69+
}
3470

35-
if (Interlocked.Read(ref cancelled) == 1)
36-
{
37-
cancellationToken.ThrowIfCancellationRequested();
38-
}
39-
}
71+
private static string GetEchoLines(this System.Diagnostics.ProcessStartInfo info, string echoPrefix)
72+
{
73+
var builder = new StringBuilder();
4074

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

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

49-
if (!noEcho)
84+
foreach (var arg in info.ArgumentList)
5085
{
51-
await Console.Out.WriteAsync(process.StartInfo.GetEchoLines(echoPrefix)).ConfigureAwait(false);
86+
_ = builder.AppendLine(CultureInfo.InvariantCulture, $"{echoPrefix}: {arg}");
5287
}
53-
54-
_ = process.Start();
55-
56-
await using var register = cancellationToken.Register(
57-
() => sync.Run(
58-
() => tcs.Task.Status != TaskStatus.RanToCompletion,
59-
() =>
60-
{
61-
if (process.TryKill(cancellationIgnoresProcessTree))
62-
{
63-
_ = tcs.TrySetCanceled(cancellationToken);
64-
}
65-
}),
66-
useSynchronizationContext: false).ConfigureAwait(false);
67-
68-
await tcs.Task.ConfigureAwait(false);
6988
}
70-
71-
private static string GetEchoLines(this System.Diagnostics.ProcessStartInfo info, string echoPrefix)
89+
else
7290
{
73-
var builder = new StringBuilder();
91+
_ = builder.AppendLine(CultureInfo.InvariantCulture, $"{echoPrefix}: {info.FileName}{(string.IsNullOrEmpty(info.Arguments) ? "" : $" {info.Arguments}")}");
92+
}
7493

75-
if (!string.IsNullOrEmpty(info.WorkingDirectory))
76-
{
77-
_ = builder.AppendLine(CultureInfo.InvariantCulture, $"{echoPrefix}: Working directory: {info.WorkingDirectory}");
78-
}
94+
return builder.ToString();
95+
}
7996

80-
if (info.ArgumentList.Count > 0)
81-
{
82-
_ = builder.AppendLine(CultureInfo.InvariantCulture, $"{echoPrefix}: {info.FileName}");
97+
private static bool TryKill(this Process process, bool ignoreProcessTree)
98+
{
99+
// exceptions may be thrown for all kinds of reasons
100+
// and the _same exception_ may be thrown for all kinds of reasons
101+
// System.Diagnostics.Process is "fine"
102+
try
103+
{
104+
process.Kill(!ignoreProcessTree);
105+
}
106+
#pragma warning disable CA1031 // Do not catch general exception types
107+
catch (Exception)
108+
#pragma warning restore CA1031 // Do not catch general exception types
109+
{
110+
return false;
111+
}
83112

84-
foreach (var arg in info.ArgumentList)
85-
{
86-
_ = builder.AppendLine(CultureInfo.InvariantCulture, $"{echoPrefix}: {arg}");
87-
}
88-
}
89-
else
90-
{
91-
_ = builder.AppendLine(CultureInfo.InvariantCulture, $"{echoPrefix}: {info.FileName}{(string.IsNullOrEmpty(info.Arguments) ? "" : $" {info.Arguments}")}");
92-
}
113+
return true;
114+
}
93115

94-
return builder.ToString();
116+
private static void Run(this SemaphoreSlim sync, Func<bool> doubleCheckPredicate, Action action)
117+
{
118+
if (!doubleCheckPredicate())
119+
{
120+
return;
95121
}
96122

97-
private static bool TryKill(this Process process, bool ignoreProcessTree)
123+
try
98124
{
99-
// exceptions may be thrown for all kinds of reasons
100-
// and the _same exception_ may be thrown for all kinds of reasons
101-
// System.Diagnostics.Process is "fine"
102-
try
103-
{
104-
process.Kill(!ignoreProcessTree);
105-
}
106-
#pragma warning disable CA1031 // Do not catch general exception types
107-
catch (Exception)
108-
#pragma warning restore CA1031 // Do not catch general exception types
109-
{
110-
return false;
111-
}
112-
113-
return true;
125+
sync.Wait();
126+
}
127+
catch (ObjectDisposedException)
128+
{
129+
return;
114130
}
115131

116-
private static void Run(this SemaphoreSlim sync, Func<bool> doubleCheckPredicate, Action action)
132+
try
117133
{
118-
if (!doubleCheckPredicate())
134+
if (doubleCheckPredicate())
119135
{
120-
return;
136+
action();
121137
}
122-
138+
}
139+
finally
140+
{
123141
try
124142
{
125-
sync.Wait();
143+
_ = sync.Release();
126144
}
127145
catch (ObjectDisposedException)
128146
{
129-
return;
130-
}
131-
132-
try
133-
{
134-
if (doubleCheckPredicate())
135-
{
136-
action();
137-
}
138-
}
139-
finally
140-
{
141-
try
142-
{
143-
_ = sync.Release();
144-
}
145-
catch (ObjectDisposedException)
146-
{
147-
}
148147
}
149148
}
150149
}

0 commit comments

Comments
 (0)