diff --git a/MedallionShell.Tests/GeneralTest.cs b/MedallionShell.Tests/GeneralTest.cs index 813167d..2d3e8ae 100644 --- a/MedallionShell.Tests/GeneralTest.cs +++ b/MedallionShell.Tests/GeneralTest.cs @@ -490,6 +490,21 @@ public void TestToString() command6.ToString().ShouldEqual($"{command5} > {new StringWriter()}"); } + [TestMethod] + public void TestCommandOption() + { + var command = Command.Run("SampleCommand", new[] { "echo" }, options: o => o.Command(c => c.StandardInput.Write("!!!"))) + .RedirectFrom("abc"); + command.Wait(); + command.Result.StandardOutput.ShouldEqual("!!!abc"); + + var writer = new StringWriter(); + command = Command.Run("SampleCommand", new[] { "echo" }, options: o => o.Command(c => c.RedirectTo(writer))) + .RedirectFrom("abc123"); + command.Wait(); + writer.ToString().ShouldEqual("abc123"); + } + private IEnumerable ErrorLines() { yield return "1"; diff --git a/MedallionShell/Shell.cs b/MedallionShell/Shell.cs index abd87a4..a0d5e12 100644 --- a/MedallionShell/Shell.cs +++ b/MedallionShell/Shell.cs @@ -59,7 +59,7 @@ public Command Run(string executable, IEnumerable arguments = null, Acti } finalOptions.StartInfoInitializers.ForEach(a => a(processStartInfo)); - var command = new ProcessCommand( + Command command = new ProcessCommand( processStartInfo, throwOnError: finalOptions.ThrowExceptionOnError, disposeOnExit: finalOptions.DisposeProcessOnExit, @@ -67,8 +67,12 @@ public Command Run(string executable, IEnumerable arguments = null, Acti cancellationToken: finalOptions.ProcessCancellationToken, standardInputEncoding: finalOptions.ProcessStreamEncoding ); - finalOptions.CommandInitializers.ForEach(a => a(command)); - + foreach (var initializer in finalOptions.CommandInitializers) + { + command = initializer(command); + if (command == null) { throw new InvalidOperationException($"{nameof(Command)} initializer passed to {nameof(Options)}.{nameof(Options.Command)} must not return null!"); } + } + return command; } @@ -168,7 +172,7 @@ private Options GetOptions(Action additionalConfiguration) #region ---- Options ---- /// /// Provides a builder interface for configuring the options for creating and executing - /// a + /// a /// public sealed class Options { @@ -178,7 +182,7 @@ internal Options() } internal List> StartInfoInitializers { get; private set; } - internal List> CommandInitializers { get; private set; } + internal List> CommandInitializers { get; private set; } internal CommandLineSyntax CommandLineSyntax { get; private set; } internal bool ThrowExceptionOnError { get; private set; } internal bool DisposeProcessOnExit { get; private set; } @@ -193,7 +197,7 @@ internal Options() public Options RestoreDefaults() { this.StartInfoInitializers = new List>(); - this.CommandInitializers = new List>(); + this.CommandInitializers = new List>(); this.CommandLineSyntax = new WindowsCommandLineSyntax(); this.ThrowExceptionOnError = false; this.DisposeProcessOnExit = true; @@ -209,26 +213,42 @@ public Options RestoreDefaults() /// public Options StartInfo(Action initializer) { - Throw.IfNull(initializer, "initializer"); + Throw.IfNull(initializer, nameof(initializer)); this.StartInfoInitializers.Add(initializer); return this; } /// - /// Specifies a function which can modify the . Multiple such functions + /// Specifies a function which can modify the . Multiple such functions /// can be specified this way /// public Options Command(Action initializer) { - Throw.IfNull(initializer, "initializer"); + Throw.IfNull(initializer, nameof(initializer)); + + this.Command(c => + { + initializer(c); + return c; + }); + return this; + } + + /// + /// Specifies a function which can project the to a new . + /// Intended to be used with -producing "pipe" functions like + /// + public Options Command(Func initializer) + { + Throw.IfNull(initializer, nameof(initializer)); this.CommandInitializers.Add(initializer); return this; } /// - /// Sets the initial working directory of the (defaults to the current working directory) + /// Sets the initial working directory of the (defaults to the current working directory) /// . public Options WorkingDirectory(string path) { @@ -237,7 +257,7 @@ public Options WorkingDirectory(string path) #if !NETSTANDARD1_3 /// - /// Adds or overwrites an environment variable to be passed to the + /// Adds or overwrites an environment variable to be passed to the /// public Options EnvironmentVariable(string name, string value) { @@ -247,7 +267,7 @@ public Options EnvironmentVariable(string name, string value) } /// - /// Adds or overwrites a set of environmental variables to be passed to the + /// Adds or overwrites a set of environmental variables to be passed to the /// public Options EnvironmentVariables(IEnumerable> environmentVariables) { @@ -259,7 +279,7 @@ public Options EnvironmentVariables(IEnumerable> en #endif /// - /// If specified, a non-zero exit code will cause the 's to fail + /// If specified, a non-zero exit code will cause the 's to fail /// with . Defaults to false /// public Options ThrowOnError(bool value = true) @@ -270,7 +290,7 @@ public Options ThrowOnError(bool value = true) /// /// If specified, the underlying object for the command will be disposed when the process exits. - /// This means that there is no need to dispose of a . + /// This means that there is no need to dispose of a . /// /// This also means that cannot be reliably accessed, /// since it may exit at any time.