From 2d81affbfd34b81ce92fbb80fdf9ec0cd3f2a78b Mon Sep 17 00:00:00 2001 From: Perry Date: Mon, 25 Sep 2023 10:41:39 -0300 Subject: [PATCH 1/5] Add new properties and crude access to the internal process --- CommandExec.Tests/EnvironmentTests.cs | 14 ++--- CommandExec/Command.cs | 79 +++++++++++++++++++-------- CommandExec/CommandUtils.cs | 2 +- 3 files changed, 64 insertions(+), 31 deletions(-) diff --git a/CommandExec.Tests/EnvironmentTests.cs b/CommandExec.Tests/EnvironmentTests.cs index 83542f7..fe3f96c 100644 --- a/CommandExec.Tests/EnvironmentTests.cs +++ b/CommandExec.Tests/EnvironmentTests.cs @@ -5,24 +5,22 @@ public class EnvironmentTests [Fact(DisplayName = "Run shell command")] public void ShellCommandTest() { - Command shell = Command.Shell("echo test"); + Command shell = Command.Shell("echo", "test"); shell .RedirectStdOut() .RedirectStdErr() .Run(); - string STDOut = shell.STDOut.ReadToEnd().TrimEnd(); + string STDOut = shell.stdOut.ReadToEnd().TrimEnd(); Assert.Equal("test", STDOut); - - //! There can be errors with the shell itself, not the process. - // string STDErr = shell.STDErr.ReadToEnd().TrimEnd(); - // Assert.Equal(string.Empty, STDErr); + Assert.Equal(0, shell.exitCode); + Assert.False(shell.hasError); } [Fact(DisplayName = "Check if command exists")] public void CommandExistsTest() { - Assert.True(CommandUtils.CommandExists("type")); - Assert.False(CommandUtils.CommandExists("fake-test-command-that-is-not-real")); + Assert.True(CommandUtils.Exists("type")); + Assert.False(CommandUtils.Exists("fake-test-command-that-is-not-real")); } } diff --git a/CommandExec/Command.cs b/CommandExec/Command.cs index d0d89de..aced21e 100644 --- a/CommandExec/Command.cs +++ b/CommandExec/Command.cs @@ -1,3 +1,4 @@ +using System; using System.Collections; using System.Diagnostics; using System.IO; @@ -17,19 +18,39 @@ public class Command : IEnumerable internal static readonly bool isUnix = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows); #endregion - #region Standard Streams + #region Properties /// /// Gets the for the Standard Output stream. /// - public StreamReader STDOut => process.StandardOutput; + public StreamReader stdOut => process.StandardOutput; /// /// Gets the for the Standard Input stream. /// - public StreamWriter STDIn => process.StandardInput; + public StreamWriter stdIn => process.StandardInput; /// /// Gets the for the Standard Error stream. /// - public StreamReader STDErr => process.StandardError; + public StreamReader stdErr => process.StandardError; + /// + /// Gets the exit code of the process. + /// + public int exitCode => process.ExitCode; + /// + /// Gets if the process had an error during execution. + /// + public bool hasError => exitCode != 0; + /// + /// Executed when the process exits. + /// + public event Action? onExit; + /// + /// Gets the id of the process. + /// + public int id => process.Id; + /// + /// Gets the start time of the process. + /// + public DateTime startTime => process.StartTime; #endregion #region Functions @@ -38,15 +59,15 @@ public class Command : IEnumerable /// /// The file to run. /// The working directory of the process. If null, defaults to the current working directory. - /// Whether the textual output of an application is written to . - /// Wether the input for an application is read from . - /// Whether the error output of an application is written to . + /// Whether the textual output of an application is written to . + /// Wether the input for an application is read from . + /// Whether the error output of an application is written to . /// The process arguments. public Command(string command, string? cwd = null, bool redirectSTDOut = false, bool redirectSTDIn = false, bool redirectSTDErr = false, params string[] args) { process = new Process(); process.StartInfo.FileName = command; - + process.Exited += OnExit; process.StartInfo.WorkingDirectory = cwd ?? Directory.GetCurrentDirectory(); process.StartInfo.CreateNoWindow = true; process.StartInfo.UseShellExecute = false; @@ -81,6 +102,18 @@ public Task RunAsync(params string[] args) return process.WaitForExitAsync(); } + /// + /// Runs the process. + /// + /// + /// The process instance. + /// + public Process RawStart() + { + process.Start(); + return process; + } + /// /// Runs a command inside a shell. CMD on WIndows and Bash everywhere else. /// @@ -89,22 +122,17 @@ public Task RunAsync(params string[] args) /// The command used to run the shell. public static Command Shell(params string[] args) { - (string shellCommand, string shellArg) = isUnix ? ("bash", "-c") : ("powershell", string.Empty); + (string shellCommand, string shellArg) = isUnix ? ("/bin/sh", "-c") : ("powershell", "-Command"); Command shell = new Command(shellCommand) .AddArg(shellArg); - if (isUnix) - { - return shell.AddArg($"\"{string.Join(" ", args).Replace("\"", "\\\"")}"); - } - return shell.AddArg(args); - + return shell.AddArg($"\"{string.Join(" ", args).Replace("\"", "\\\"")}"); } /// /// Adds a argument to the command. /// - /// The argument to add. + /// The argument to add. /// The command instance (for chaining). /// /// is an alias for . @@ -128,9 +156,9 @@ public Command CWD(string dir) } /// - /// Sets whether the textual output of an application is written to . + /// Sets whether the textual output of an application is written to . /// - /// Whether the textual output of an application is written to . + /// Whether the textual output of an application is written to . /// The command instance (for chaining). public Command RedirectStdOut(bool redirect = true) { @@ -139,9 +167,9 @@ public Command RedirectStdOut(bool redirect = true) } /// - /// Sets whether the input for an application is read from . + /// Sets whether the input for an application is read from . /// - /// Whether the input for an application is read from . + /// Whether the input for an application is read from . /// The command instance (for chaining). public Command RedirectStdIn(bool redirect = true) { @@ -150,9 +178,9 @@ public Command RedirectStdIn(bool redirect = true) } /// - /// Sets whether the error output of an application is written to . + /// Sets whether the error output of an application is written to . /// - /// Whether the error output of an application is written to . + /// Whether the error output of an application is written to . /// The command instance (for chaining). public Command RedirectStdErr(bool redirect = true) { @@ -161,6 +189,13 @@ public Command RedirectStdErr(bool redirect = true) } #endregion + #region Private Functions + void OnExit(object? sender, EventArgs e) + { + onExit?.Invoke(process); + } + #endregion + #region Required Functions /// /// Adds a argument to the command. diff --git a/CommandExec/CommandUtils.cs b/CommandExec/CommandUtils.cs index 4b1b839..8002db9 100644 --- a/CommandExec/CommandUtils.cs +++ b/CommandExec/CommandUtils.cs @@ -2,7 +2,7 @@ namespace CommandExec { public static class CommandUtils { - public static bool CommandExists(string command) + public static bool Exists(string command) { Command cmd; if (Command.isUnix) From cfd15ef703bbdc5512decb39205fc7c3495e8be2 Mon Sep 17 00:00:00 2001 From: Perry Date: Tue, 26 Sep 2023 11:32:34 -0300 Subject: [PATCH 2/5] Fix errors --- .github/workflows/test.yml | 20 ++++++++++++++++---- CommandExec.Tests/EnvironmentTests.cs | 8 ++++++++ CommandExec/Command.cs | 13 ++++++++----- CommandExec/CommandExec.csproj | 9 ++++++++- CommandExec/CommandUtils.cs | 22 +++++++--------------- 5 files changed, 47 insertions(+), 25 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 696be5a..3012251 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,18 +17,30 @@ jobs: uses: actions/setup-dotnet@v3 with: dotnet-version: 7.0.x + - name: Restore + run: dotnet restore + - name: Build Main Library + run: dotnet build ./CommandExec/CommandExec.csproj -r win10-x64 --no-restore + - name: Build Tests + run: dotnet build ./CommandExec.Tests/CommandExec.Tests.csproj --no-restore - name: Run Tests - run: dotnet test --verbosity normal - + run: dotnet test --verbosity normal --no-build + test-on-linux: runs-on: ubuntu-latest - + steps: - uses: actions/checkout@v3 - name: Setup .NET uses: actions/setup-dotnet@v3 with: dotnet-version: 7.0.x + - name: Restore + run: dotnet restore + - name: Build Main Library + run: dotnet build ./CommandExec/CommandExec.csproj -r linux-x64 --no-restore + - name: Build Tests + run: dotnet build ./CommandExec.Tests/CommandExec.Tests.csproj --no-restore - name: Run Tests - run: dotnet test --verbosity normal + run: dotnet test --verbosity normal --no-build diff --git a/CommandExec.Tests/EnvironmentTests.cs b/CommandExec.Tests/EnvironmentTests.cs index fe3f96c..8fc42d6 100644 --- a/CommandExec.Tests/EnvironmentTests.cs +++ b/CommandExec.Tests/EnvironmentTests.cs @@ -1,3 +1,5 @@ +using System.Runtime.InteropServices; + namespace CommandExec.Tests; public class EnvironmentTests @@ -20,6 +22,12 @@ public void ShellCommandTest() [Fact(DisplayName = "Check if command exists")] public void CommandExistsTest() { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Command cmd = Command.Shell("file", "/bin/sh").RedirectStdOut(); + cmd.Run(); + Assert.Equal("/bin/sh: symbolic link to", cmd.stdOut.ReadToEnd()[..25]); + } Assert.True(CommandUtils.Exists("type")); Assert.False(CommandUtils.Exists("fake-test-command-that-is-not-real")); } diff --git a/CommandExec/Command.cs b/CommandExec/Command.cs index aced21e..cf95d9c 100644 --- a/CommandExec/Command.cs +++ b/CommandExec/Command.cs @@ -15,7 +15,6 @@ public class Command : IEnumerable #region Fields string args; internal readonly Process process; - internal static readonly bool isUnix = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows); #endregion #region Properties @@ -122,10 +121,14 @@ public Process RawStart() /// The command used to run the shell. public static Command Shell(params string[] args) { - (string shellCommand, string shellArg) = isUnix ? ("/bin/sh", "-c") : ("powershell", "-Command"); - Command shell = new Command(shellCommand) - .AddArg(shellArg); - + (string shellCommand, string shellArg) = +#if !WINDOWS + ("/bin/sh", "-c"); +#else + ("powershell", "-Command"); +#endif + + Command shell = new Command(shellCommand).AddArg(shellArg); return shell.AddArg($"\"{string.Join(" ", args).Replace("\"", "\\\"")}"); } diff --git a/CommandExec/CommandExec.csproj b/CommandExec/CommandExec.csproj index 5fa7149..fc068f3 100644 --- a/CommandExec/CommandExec.csproj +++ b/CommandExec/CommandExec.csproj @@ -16,7 +16,14 @@ CommandExec true true - IDE1006;IDE0090 + + win10-x64;win10-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64 + + + win10-x64;linux-x64;osx-x64;osx-arm64 + + false + $(NoWarn);IDE1006;IDE0090 diff --git a/CommandExec/CommandUtils.cs b/CommandExec/CommandUtils.cs index 8002db9..5ccc8b1 100644 --- a/CommandExec/CommandUtils.cs +++ b/CommandExec/CommandUtils.cs @@ -5,23 +5,15 @@ public static class CommandUtils public static bool Exists(string command) { Command cmd; - if (Command.isUnix) - { - cmd = Command.Shell($"command -v {command} &> /dev/null").RedirectStdOut() - .RedirectStdOut() - .RedirectStdErr() - .RedirectStdIn(); - } - else - { - cmd = Command.Shell($"Get-Command -Name {command} -ErrorAction SilentlyContinue > $null") - .RedirectStdOut() - .RedirectStdErr() - .RedirectStdIn(); - } +#if !WINDOWS + cmd = Command.Shell($"command -v {command}").RedirectStdOut() +#else + cmd = Command.Shell($"Get-Command -Name {command} -ErrorAction SilentlyContinue > $null") +#endif + .RedirectStdOut().RedirectStdErr().RedirectStdIn(); cmd.Run(); - return cmd.process.ExitCode == 0; + return !cmd.hasError; } } } From a1f7962ebf3a70aaee90295c237845b6c0d442bd Mon Sep 17 00:00:00 2001 From: Perry Date: Sun, 1 Oct 2023 01:06:04 -0300 Subject: [PATCH 3/5] changes nothing --- .github/workflows/test.yml | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3012251..1294354 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,19 +12,17 @@ jobs: runs-on: windows-latest steps: - - uses: actions/checkout@v3 - - name: Setup .NET - uses: actions/setup-dotnet@v3 - with: - dotnet-version: 7.0.x - - name: Restore - run: dotnet restore - - name: Build Main Library - run: dotnet build ./CommandExec/CommandExec.csproj -r win10-x64 --no-restore - - name: Build Tests - run: dotnet build ./CommandExec.Tests/CommandExec.Tests.csproj --no-restore - - name: Run Tests - run: dotnet test --verbosity normal --no-build + - uses: actions/checkout@v3 + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 7.0.x + - name: Restore + run: dotnet restore + - name: Build + run: dotnet build + - name: Run Tests + run: dotnet test --verbosity normal --no-build test-on-linux: @@ -38,9 +36,7 @@ jobs: dotnet-version: 7.0.x - name: Restore run: dotnet restore - - name: Build Main Library - run: dotnet build ./CommandExec/CommandExec.csproj -r linux-x64 --no-restore - - name: Build Tests - run: dotnet build ./CommandExec.Tests/CommandExec.Tests.csproj --no-restore + - name: Build + run: dotnet build - name: Run Tests run: dotnet test --verbosity normal --no-build From 5805bd36cc44ca145abb83924a96c4024535914a Mon Sep 17 00:00:00 2001 From: Perry Date: Sun, 1 Oct 2023 01:06:49 -0300 Subject: [PATCH 4/5] Remove uneccessary property on main csproj --- CommandExec/CommandExec.csproj | 7 ------- 1 file changed, 7 deletions(-) diff --git a/CommandExec/CommandExec.csproj b/CommandExec/CommandExec.csproj index fc068f3..d5489c8 100644 --- a/CommandExec/CommandExec.csproj +++ b/CommandExec/CommandExec.csproj @@ -16,13 +16,6 @@ CommandExec true true - - win10-x64;win10-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64 - - - win10-x64;linux-x64;osx-x64;osx-arm64 - - false $(NoWarn);IDE1006;IDE0090 From a37c8b40711edf77343e3c1c2ff4c18d767d6324 Mon Sep 17 00:00:00 2001 From: Perry Date: Sun, 1 Oct 2023 01:09:15 -0300 Subject: [PATCH 5/5] Changes `Shell` function to be in the CommandUtils class --- CommandExec.Tests/EnvironmentTests.cs | 7 +++--- CommandExec/Command.cs | 20 +---------------- CommandExec/CommandUtils.cs | 32 ++++++++++++++++++++++----- 3 files changed, 30 insertions(+), 29 deletions(-) diff --git a/CommandExec.Tests/EnvironmentTests.cs b/CommandExec.Tests/EnvironmentTests.cs index 8fc42d6..f3ac4f5 100644 --- a/CommandExec.Tests/EnvironmentTests.cs +++ b/CommandExec.Tests/EnvironmentTests.cs @@ -7,14 +7,13 @@ public class EnvironmentTests [Fact(DisplayName = "Run shell command")] public void ShellCommandTest() { - Command shell = Command.Shell("echo", "test"); + Command shell = CommandUtils.Shell("echo", "test"); shell .RedirectStdOut() .RedirectStdErr() .Run(); - string STDOut = shell.stdOut.ReadToEnd().TrimEnd(); - Assert.Equal("test", STDOut); + Assert.Equal("test", shell.stdOut.ReadToEnd().TrimEnd()); Assert.Equal(0, shell.exitCode); Assert.False(shell.hasError); } @@ -24,7 +23,7 @@ public void CommandExistsTest() { if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - Command cmd = Command.Shell("file", "/bin/sh").RedirectStdOut(); + Command cmd = CommandUtils.Shell("file", "/bin/sh").RedirectStdOut(); cmd.Run(); Assert.Equal("/bin/sh: symbolic link to", cmd.stdOut.ReadToEnd()[..25]); } diff --git a/CommandExec/Command.cs b/CommandExec/Command.cs index cf95d9c..e9ea57f 100644 --- a/CommandExec/Command.cs +++ b/CommandExec/Command.cs @@ -15,6 +15,7 @@ public class Command : IEnumerable #region Fields string args; internal readonly Process process; + internal static readonly bool isUnix = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows); #endregion #region Properties @@ -113,25 +114,6 @@ public Process RawStart() return process; } - /// - /// Runs a command inside a shell. CMD on WIndows and Bash everywhere else. - /// - /// The command to run in the shell. - /// Additional arguments passed to the shell command. - /// The command used to run the shell. - public static Command Shell(params string[] args) - { - (string shellCommand, string shellArg) = -#if !WINDOWS - ("/bin/sh", "-c"); -#else - ("powershell", "-Command"); -#endif - - Command shell = new Command(shellCommand).AddArg(shellArg); - return shell.AddArg($"\"{string.Join(" ", args).Replace("\"", "\\\"")}"); - } - /// /// Adds a argument to the command. /// diff --git a/CommandExec/CommandUtils.cs b/CommandExec/CommandUtils.cs index 5ccc8b1..69b88a6 100644 --- a/CommandExec/CommandUtils.cs +++ b/CommandExec/CommandUtils.cs @@ -2,16 +2,36 @@ namespace CommandExec { public static class CommandUtils { + /// + /// Runs a command inside a shell. CMD on WIndows and Bash everywhere else. + /// + /// The command to run in the shell. + /// Additional arguments passed to the shell command. + /// The command used to run the shell. + public static Command Shell(params string[] args) + { + (string shellCommand, string shellArg) = Command.isUnix ? + ("/bin/sh", "-c") : + ("powershell", "-Command"); + + Command shell = new Command(shellCommand).AddArg(shellArg); + return shell.AddArg($"\"{string.Join(" ", args).Replace("\"", "\\\"")}"); + } + public static bool Exists(string command) { Command cmd; -#if !WINDOWS - cmd = Command.Shell($"command -v {command}").RedirectStdOut() -#else - cmd = Command.Shell($"Get-Command -Name {command} -ErrorAction SilentlyContinue > $null") -#endif - .RedirectStdOut().RedirectStdErr().RedirectStdIn(); + if (Command.isUnix) + { + cmd = Shell($"command", "-v", command).RedirectStdOut().RedirectStdErr() + .RedirectStdOut().RedirectStdErr(); + cmd.Run(); + return !cmd.hasError; + } + + cmd = Shell("Get-Command", "-Name", command, "-ErrorAction", "Stop") + .RedirectStdOut().RedirectStdErr(); cmd.Run(); return !cmd.hasError; }