Skip to content

Commit

Permalink
StartProgram now takes handlers directly
Browse files Browse the repository at this point in the history
  • Loading branch information
Kaoticz committed May 23, 2024
1 parent 9295b41 commit 7969ba1
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 22 deletions.
82 changes: 66 additions & 16 deletions Kotz.Extensions/KotzUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public static bool ProgramExists(string programName)
{
ArgumentException.ThrowIfNullOrEmpty(programName, nameof(programName));

using var process = StartProcess(_programVerifier, programName, false, false);
using var process = StartProcess(_programVerifier, programName, [(_, _) => { }], [(_, _) => { }]);
process.WaitForExit();

return process.ExitCode is 0;
Expand All @@ -104,33 +104,40 @@ public static bool ProgramExists(string programName)
/// or the absolute path to its executable.
/// </param>
/// <param name="arguments">The arguments to the program.</param>
/// <param name="redirectStdout">Determines whether Standard Output should be redirected.</param>
/// <param name="redirectStderr">Determines whether Standard Error should be redirected.</param>
/// <param name="stdoutRedirectHandlers">Defines the handlers for redirected Standard Output data.</param>
/// <param name="stderrRedirectHandlers">Defines the handlers for redirected Standard Error data.</param>
/// <remarks>
/// The <paramref name="arguments"/> parameter is not escaped, you can either escape it yourself or
/// use <see cref="StartProcess(string, IEnumerable{string}, bool, bool)"/> instead.
/// The <paramref name="arguments"/> parameter is not escaped, you can either escape it yourself or use
/// <see cref="StartProcess(string, IEnumerable{string}, IReadOnlyCollection{DataReceivedEventHandler}?, IReadOnlyCollection{DataReceivedEventHandler}?)"/>
/// instead.
/// </remarks>
/// <returns>The process of the specified program.</returns>
/// <exception cref="ArgumentException" />
/// <exception cref="ArgumentNullException" />
/// <exception cref="Win32Exception">Occurs when <paramref name="program"/> does not exist.</exception>
/// <exception cref="InvalidOperationException">Occurs when the process fails to execute.</exception>
public static Process StartProcess(string program, string arguments = "", bool redirectStdout = true, bool redirectStderr = true)
public static Process StartProcess(
string program,
string arguments = "",
IReadOnlyCollection<DataReceivedEventHandler>? stdoutRedirectHandlers = default,
IReadOnlyCollection<DataReceivedEventHandler>? stderrRedirectHandlers = default
)
{
ArgumentException.ThrowIfNullOrEmpty(program, nameof(program));
ArgumentNullException.ThrowIfNull(arguments, nameof(arguments));

return Process.Start(new ProcessStartInfo()
var process = Process.Start(new ProcessStartInfo()
{
FileName = program,
Arguments = arguments,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = redirectStdout,
RedirectStandardError = redirectStderr
RedirectStandardOutput = stdoutRedirectHandlers is { Count: not 0 },
RedirectStandardError = stderrRedirectHandlers is { Count: not 0 }
}) ?? throw new InvalidOperationException($"Failed spawing process for: {program} {arguments}");
}

return EnableProcessEvents(process, stdoutRedirectHandlers, stderrRedirectHandlers);
}

/// <summary>
/// Starts the specified program in the background.
Expand All @@ -140,15 +147,20 @@ public static Process StartProcess(string program, string arguments = "", bool r
/// or the absolute path to its executable.
/// </param>
/// <param name="arguments">The arguments to the program.</param>
/// <param name="redirectStdout">Determines whether Standard Output should be redirected.</param>
/// <param name="redirectStderr">Determines whether Standard Error should be redirected.</param>
/// <param name="stdoutRedirectHandlers">Defines the handlers for redirected Standard Output data.</param>
/// <param name="stderrRedirectHandlers">Defines the handlers for redirected Standard Error data.</param>
/// <remarks>The <paramref name="arguments"/> get automatically escaped.</remarks>
/// <returns>The process of the specified program.</returns>
/// <exception cref="ArgumentException" />
/// <exception cref="ArgumentNullException" />
/// <exception cref="Win32Exception">Occurs when <paramref name="program"/> does not exist.</exception>
/// <exception cref="InvalidOperationException">Occurs when the process fails to execute.</exception>
public static Process StartProcess(string program, IEnumerable<string> arguments, bool redirectStdout = true, bool redirectStderr = true)
public static Process StartProcess(
string program,
IEnumerable<string> arguments,
IReadOnlyCollection<DataReceivedEventHandler>? stdoutRedirectHandlers = default,
IReadOnlyCollection<DataReceivedEventHandler>? stderrRedirectHandlers = default
)
{
ArgumentException.ThrowIfNullOrEmpty(program, nameof(program));
ArgumentNullException.ThrowIfNull(arguments, nameof(arguments));
Expand All @@ -158,15 +170,17 @@ public static Process StartProcess(string program, IEnumerable<string> arguments
FileName = program,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = redirectStdout,
RedirectStandardError = redirectStderr
RedirectStandardOutput = stdoutRedirectHandlers is { Count: not 0 },
RedirectStandardError = stderrRedirectHandlers is { Count: not 0 }
};

foreach (var argument in arguments)
processInfo.ArgumentList.Add(argument);

return Process.Start(processInfo)
var process = Process.Start(processInfo)
?? throw new InvalidOperationException($"Failed spawing process for: {program} {string.Join(' ', processInfo.ArgumentList)}");

return EnableProcessEvents(process, stdoutRedirectHandlers, stderrRedirectHandlers);
}

/// <summary>
Expand Down Expand Up @@ -355,6 +369,42 @@ public static bool TryMoveDirectory(string source, string destination)
}
}

/// <summary>
/// Enables event raising for the specified handlers, if there are any,
/// </summary>
/// <param name="process">The process to enable events.</param>
/// <param name="stdoutRedirectHandlers">The handlers for redirected Standard Output data.</param>
/// <param name="stderrRedirectHandlers">The handlers for redirected Standard Error data.</param>
/// <returns>The <paramref name="process"/> with events enabled or not if no handlers are provided.</returns>
private static Process EnableProcessEvents(
Process process,
IReadOnlyCollection<DataReceivedEventHandler>? stdoutRedirectHandlers,
IReadOnlyCollection<DataReceivedEventHandler>? stderrRedirectHandlers
)
{
if (stdoutRedirectHandlers is { Count: not 0 })
{
process.EnableRaisingEvents = true;

foreach (var handler in stdoutRedirectHandlers)
process.OutputDataReceived += handler;

process.BeginOutputReadLine();
}

if (stderrRedirectHandlers is { Count: not 0 })
{
process.EnableRaisingEvents = true;

foreach (var handler in stderrRedirectHandlers)
process.ErrorDataReceived += handler;

process.BeginErrorReadLine();
}

return process;
}

/// <summary>
/// Moves a directory from <paramref name="source"/> to <paramref name="destination"/>.
/// </summary>
Expand Down
18 changes: 14 additions & 4 deletions Kotz.Extensions/StringBuilderExt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,13 @@ public static StringBuilder TrimEnd(this StringBuilder stringBuilder, char trimC
if (stringBuilder.Length is 0)
return stringBuilder;

while (stringBuilder.Length is not 0 && stringBuilder[^1] == trimChar)
stringBuilder.Remove(stringBuilder.Length - 1, 1);
var counter = 0;

for (var index = stringBuilder.Length - 1; index > 0 && stringBuilder[index] == trimChar; index--)
counter++;

if (counter > 0)
stringBuilder.Remove(stringBuilder.Length - counter, counter);

return stringBuilder;
}
Expand All @@ -195,8 +200,13 @@ public static StringBuilder TrimStart(this StringBuilder stringBuilder, char tri
if (stringBuilder.Length is 0)
return stringBuilder;

while (stringBuilder.Length is not 0 && stringBuilder[0] == trimChar)
stringBuilder.Remove(0, 1);
var counter = 0;

for (var index = 0; index < stringBuilder.Length - 1 && stringBuilder[index] == trimChar; index++)
counter++;

if (counter > 0)
stringBuilder.Remove(0, counter);

return stringBuilder;
}
Expand Down
4 changes: 2 additions & 2 deletions Kotz.Tests/Extensions/Utilities/StartProgramTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ public sealed class StartProgramTests
[Fact]
internal void StartProgramSuccessTest()
{
using var process1 = KotzUtilities.StartProcess("echo", "Hello from xUnit!");
using var process2 = KotzUtilities.StartProcess("echo", ["Hello from xUnit!"]);
using var process1 = KotzUtilities.StartProcess("echo", "Hello from xUnit!", [(_, _) => { }]);
using var process2 = KotzUtilities.StartProcess("echo", ["Hello from xUnit!"], [(_, _) => { }]);

Assert.NotNull(process1);
Assert.NotNull(process2);
Expand Down

0 comments on commit 7969ba1

Please sign in to comment.