Skip to content

Commit

Permalink
Added Command(Func<Command, Command>) option to allow for auto-piping…
Browse files Browse the repository at this point in the history
… in a shell. Resolves #39
  • Loading branch information
madelson committed Apr 11, 2019
1 parent fbaeffd commit 48f3ab9
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 14 deletions.
15 changes: 15 additions & 0 deletions MedallionShell.Tests/GeneralTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> ErrorLines()
{
yield return "1";
Expand Down
48 changes: 34 additions & 14 deletions MedallionShell/Shell.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,20 @@ public Command Run(string executable, IEnumerable<object> arguments = null, Acti
}
finalOptions.StartInfoInitializers.ForEach(a => a(processStartInfo));

var command = new ProcessCommand(
Command command = new ProcessCommand(
processStartInfo,
throwOnError: finalOptions.ThrowExceptionOnError,
disposeOnExit: finalOptions.DisposeProcessOnExit,
timeout: finalOptions.ProcessTimeout,
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;
}

Expand Down Expand Up @@ -168,7 +172,7 @@ private Options GetOptions(Action<Options> additionalConfiguration)
#region ---- Options ----
/// <summary>
/// Provides a builder interface for configuring the options for creating and executing
/// a <see cref="Command"/>
/// a <see cref="Medallion.Shell.Command"/>
/// </summary>
public sealed class Options
{
Expand All @@ -178,7 +182,7 @@ internal Options()
}

internal List<Action<ProcessStartInfo>> StartInfoInitializers { get; private set; }
internal List<Action<Command>> CommandInitializers { get; private set; }
internal List<Func<Command, Command>> CommandInitializers { get; private set; }
internal CommandLineSyntax CommandLineSyntax { get; private set; }
internal bool ThrowExceptionOnError { get; private set; }
internal bool DisposeProcessOnExit { get; private set; }
Expand All @@ -193,7 +197,7 @@ internal Options()
public Options RestoreDefaults()
{
this.StartInfoInitializers = new List<Action<ProcessStartInfo>>();
this.CommandInitializers = new List<Action<Command>>();
this.CommandInitializers = new List<Func<Command, Command>>();
this.CommandLineSyntax = new WindowsCommandLineSyntax();
this.ThrowExceptionOnError = false;
this.DisposeProcessOnExit = true;
Expand All @@ -209,26 +213,42 @@ public Options RestoreDefaults()
/// </summary>
public Options StartInfo(Action<ProcessStartInfo> initializer)
{
Throw.IfNull(initializer, "initializer");
Throw.IfNull(initializer, nameof(initializer));

this.StartInfoInitializers.Add(initializer);
return this;
}

/// <summary>
/// Specifies a function which can modify the <see cref="Command"/>. Multiple such functions
/// Specifies a function which can modify the <see cref="Medallion.Shell.Command"/>. Multiple such functions
/// can be specified this way
/// </summary>
public Options Command(Action<Command> initializer)
{
Throw.IfNull(initializer, "initializer");
Throw.IfNull(initializer, nameof(initializer));

this.Command(c =>
{
initializer(c);
return c;
});
return this;
}

/// <summary>
/// Specifies a function which can project the <see cref="Medallion.Shell.Command"/> to a new <see cref="Medallion.Shell.Command"/>.
/// Intended to be used with <see cref="Medallion.Shell.Command"/>-producing "pipe" functions like <see cref="Medallion.Shell.Command.RedirectTo(ICollection{char})"/>
/// </summary>
public Options Command(Func<Command, Command> initializer)
{
Throw.IfNull(initializer, nameof(initializer));

this.CommandInitializers.Add(initializer);
return this;
}

/// <summary>
/// Sets the initial working directory of the <see cref="Command"/> (defaults to the current working directory)
/// Sets the initial working directory of the <see cref="Medallion.Shell.Command"/> (defaults to the current working directory)
/// </summary>.
public Options WorkingDirectory(string path)
{
Expand All @@ -237,7 +257,7 @@ public Options WorkingDirectory(string path)

#if !NETSTANDARD1_3
/// <summary>
/// Adds or overwrites an environment variable to be passed to the <see cref="Command"/>
/// Adds or overwrites an environment variable to be passed to the <see cref="Medallion.Shell.Command"/>
/// </summary>
public Options EnvironmentVariable(string name, string value)
{
Expand All @@ -247,7 +267,7 @@ public Options EnvironmentVariable(string name, string value)
}

/// <summary>
/// Adds or overwrites a set of environmental variables to be passed to the <see cref="Command"/>
/// Adds or overwrites a set of environmental variables to be passed to the <see cref="Medallion.Shell.Command"/>
/// </summary>
public Options EnvironmentVariables(IEnumerable<KeyValuePair<string, string>> environmentVariables)
{
Expand All @@ -259,7 +279,7 @@ public Options EnvironmentVariables(IEnumerable<KeyValuePair<string, string>> en
#endif

/// <summary>
/// If specified, a non-zero exit code will cause the <see cref="Command"/>'s <see cref="Task"/> to fail
/// If specified, a non-zero exit code will cause the <see cref="Medallion.Shell.Command"/>'s <see cref="Task"/> to fail
/// with <see cref="ErrorExitCodeException"/>. Defaults to false
/// </summary>
public Options ThrowOnError(bool value = true)
Expand All @@ -270,7 +290,7 @@ public Options ThrowOnError(bool value = true)

/// <summary>
/// If specified, the underlying <see cref="Process"/> object for the command will be disposed when the process exits.
/// This means that there is no need to dispose of a <see cref="Command"/>.
/// This means that there is no need to dispose of a <see cref="Medallion.Shell.Command"/>.
///
/// This also means that <see cref="Medallion.Shell.Command.Process"/> cannot be reliably accessed,
/// since it may exit at any time.
Expand Down

0 comments on commit 48f3ab9

Please sign in to comment.