Skip to content

Commit

Permalink
Merge pull request #21 from madelson/release-1.5
Browse files Browse the repository at this point in the history
Release 1.5
  • Loading branch information
madelson committed Aug 18, 2017
2 parents 5569ebb + fdd1eff commit ac89d81
Show file tree
Hide file tree
Showing 12 changed files with 139 additions and 83 deletions.
27 changes: 27 additions & 0 deletions MedallionShell.Tests/GeneralTest.cs
@@ -1,6 +1,7 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Management;
using System.Reflection;
Expand Down Expand Up @@ -466,6 +467,32 @@ string getCommandLine(int processId)
testHelper(disposeOnExit: false);
}

[TestMethod]
public void TestToString()
{
var command0 = Command.Run("SampleCommand", new[] { "grep", "a+" }, options => options.DisposeOnExit(true));
command0.ToString().ShouldEqual($"SampleCommand grep a+");

var command1 = Command.Run("SampleCommand", "exit", 0);
command1.ToString().ShouldEqual("SampleCommand exit 0");

var command2 = Command.Run("SampleCommand", "ex it", "0 0");
command2.ToString().ShouldEqual("SampleCommand \"ex it\" \"0 0\"");

var command3 = command1 < new[] { "a" };
command3.ToString().ShouldEqual("SampleCommand exit 0 < System.String[]");

var command4 = command3 | Command.Run("SampleCommand", "echo");
command4.ToString().ShouldEqual("SampleCommand exit 0 < System.String[] | SampleCommand echo");

var command5 = command2.RedirectStandardErrorTo(Stream.Null);
command5.ToString().ShouldEqual($"SampleCommand \"ex it\" \"0 0\" 2> {Stream.Null}");

var command6 = command5.RedirectTo(new StringWriter());
command6.Wait();
command6.ToString().ShouldEqual($"{command5} > {new StringWriter()}");
}

private IEnumerable<string> ErrorLines()
{
yield return "1";
Expand Down
15 changes: 10 additions & 5 deletions MedallionShell.Tests/SyntaxTest.cs
Expand Up @@ -16,16 +16,21 @@ public void DirectTestWindowsSyntax()
this.TestSyntax(" ");
this.TestSyntax(@"c:\temp", @"a\\b");
this.TestSyntax("\" a \"", @"\\", @"\""", @"\\""");
this.TestSyntax("a\"b");
this.TestSyntax("a\"b\"");
this.TestSyntax("a\"b", "c\"d");
this.TestSyntax("\v", "\t");
this.TestSyntax("\r", "\n", "\r\n");
this.TestSyntax(string.Empty, "\"", "\\", string.Empty);
this.TestSyntax("abc", "a\\b", "a\\ b\"");
}

private void TestSyntax(params string[] arguments)
{
var lines = Command.Run("SampleCommand", new[] { "argecho" }.Concat(arguments), o => o.ThrowOnError())
.StandardOutput
.GetLines()
.ToArray();
var output = Command.Run("SampleCommand", new[] { "argecho" }.Concat(arguments), o => o.ThrowOnError()).Result.StandardOutput;

lines.SequenceEqual(arguments).ShouldEqual(true, "Got: '" + string.Join(Environment.NewLine, lines) + "'");
var expected = string.Join(string.Empty, arguments.Select(a => a + Environment.NewLine));
output.ShouldEqual(expected);
}
}
}
34 changes: 17 additions & 17 deletions MedallionShell/Command.cs
Expand Up @@ -68,14 +68,14 @@ public abstract class Command : IDisposable
/// this will throw the faulting <see cref="Exception"/> or <see cref="TaskCanceledException"/> rather than
/// the wrapped <see cref="AggregateException"/> thrown by <see cref="Task{TResult}.Result"/>
/// </summary>
public void Wait() => this.Task.GetResultWithUnwrappedException();
public void Wait() => this.Task.GetAwaiter().GetResult();

/// <summary>
/// A convenience method for <code>command.Task.Result</code>. If the task faulted or was canceled,
/// this will throw the faulting <see cref="Exception"/> or <see cref="TaskCanceledException"/> rather than
/// the wrapped <see cref="AggregateException"/> thrown by <see cref="Task{TResult}.Result"/>
/// </summary>
public CommandResult Result => this.Task.GetResultWithUnwrappedException();
public CommandResult Result => this.Task.GetAwaiter().GetResult();

/// <summary>
/// A <see cref="Task"/> representing the progress of this <see cref="Command"/>
Expand Down Expand Up @@ -104,7 +104,7 @@ public Command RedirectTo(Stream stream)
{
Throw.IfNull(stream, nameof(stream));

return new IoCommand(this, this.StandardOutput.PipeToAsync(stream, leaveStreamOpen: true));
return new IoCommand(this, this.StandardOutput.PipeToAsync(stream, leaveStreamOpen: true), ">", stream);
}

/// <summary>
Expand All @@ -116,7 +116,7 @@ public Command RedirectStandardErrorTo(Stream stream)
{
Throw.IfNull(stream, nameof(stream));

return new IoCommand(this, this.StandardError.PipeToAsync(stream, leaveStreamOpen: true));
return new IoCommand(this, this.StandardError.PipeToAsync(stream, leaveStreamOpen: true), "2>", stream);
}

/// <summary>
Expand All @@ -128,7 +128,7 @@ public Command RedirectFrom(Stream stream)
{
Throw.IfNull(stream, nameof(stream));

return new IoCommand(this, this.StandardInput.PipeFromAsync(stream, leaveStreamOpen: true));
return new IoCommand(this, this.StandardInput.PipeFromAsync(stream, leaveStreamOpen: true), "<", stream);
}

/// <summary>
Expand All @@ -140,7 +140,7 @@ public Command RedirectTo(FileInfo file)
{
Throw.IfNull(file, nameof(file));

return new IoCommand(this, this.StandardOutput.PipeToAsync(file));
return new IoCommand(this, this.StandardOutput.PipeToAsync(file), ">", file);
}

/// <summary>
Expand All @@ -152,7 +152,7 @@ public Command RedirectStandardErrorTo(FileInfo file)
{
Throw.IfNull(file, nameof(file));

return new IoCommand(this, this.StandardError.PipeToAsync(file));
return new IoCommand(this, this.StandardError.PipeToAsync(file), "2>", file);
}

/// <summary>
Expand All @@ -164,7 +164,7 @@ public Command RedirectFrom(FileInfo file)
{
Throw.IfNull(file, nameof(file));

return new IoCommand(this, this.StandardInput.PipeFromAsync(file));
return new IoCommand(this, this.StandardInput.PipeFromAsync(file), "<", file);
}

/// <summary>
Expand All @@ -176,7 +176,7 @@ public Command RedirectTo(ICollection<string> lines)
{
Throw.IfNull(lines, nameof(lines));

return new IoCommand(this, this.StandardOutput.PipeToAsync(lines));
return new IoCommand(this, this.StandardOutput.PipeToAsync(lines), ">", lines.GetType());
}

/// <summary>
Expand All @@ -188,7 +188,7 @@ public Command RedirectStandardErrorTo(ICollection<string> lines)
{
Throw.IfNull(lines, nameof(lines));

return new IoCommand(this, this.StandardError.PipeToAsync(lines));
return new IoCommand(this, this.StandardError.PipeToAsync(lines), "2>", lines.GetType());
}

/// <summary>
Expand All @@ -200,7 +200,7 @@ public Command RedirectFrom(IEnumerable<string> lines)
{
Throw.IfNull(lines, nameof(lines));

return new IoCommand(this, this.StandardInput.PipeFromAsync(lines));
return new IoCommand(this, this.StandardInput.PipeFromAsync(lines), "<", lines.GetType());
}

/// <summary>
Expand All @@ -212,7 +212,7 @@ public Command RedirectTo(ICollection<char> chars)
{
Throw.IfNull(chars, nameof(chars));

return new IoCommand(this, this.StandardOutput.PipeToAsync(chars));
return new IoCommand(this, this.StandardOutput.PipeToAsync(chars), ">", chars.GetType());
}

/// <summary>
Expand All @@ -224,7 +224,7 @@ public Command RedirectStandardErrorTo(ICollection<char> chars)
{
Throw.IfNull(chars, nameof(chars));

return new IoCommand(this, this.StandardError.PipeToAsync(chars));
return new IoCommand(this, this.StandardError.PipeToAsync(chars), "2>", chars.GetType());
}

/// <summary>
Expand All @@ -236,7 +236,7 @@ public Command RedirectFrom(IEnumerable<char> chars)
{
Throw.IfNull(chars, nameof(chars));

return new IoCommand(this, this.StandardInput.PipeFromAsync(chars));
return new IoCommand(this, this.StandardInput.PipeFromAsync(chars), "<", chars.GetType());
}

/// <summary>
Expand All @@ -248,7 +248,7 @@ public Command RedirectTo(TextWriter writer)
{
Throw.IfNull(writer, nameof(writer));

return new IoCommand(this, this.StandardOutput.PipeToAsync(writer, leaveWriterOpen: true));
return new IoCommand(this, this.StandardOutput.PipeToAsync(writer, leaveWriterOpen: true), ">", writer);
}

/// <summary>
Expand All @@ -260,7 +260,7 @@ public Command RedirectStandardErrorTo(TextWriter writer)
{
Throw.IfNull(writer, nameof(writer));

return new IoCommand(this, this.StandardError.PipeToAsync(writer, leaveWriterOpen: true));
return new IoCommand(this, this.StandardError.PipeToAsync(writer, leaveWriterOpen: true), "2>", writer);
}

/// <summary>
Expand All @@ -272,7 +272,7 @@ public Command RedirectFrom(TextReader reader)
{
Throw.IfNull(reader, nameof(reader));

return new IoCommand(this, this.StandardInput.PipeFromAsync(reader, leaveReaderOpen: true));
return new IoCommand(this, this.StandardInput.PipeFromAsync(reader, leaveReaderOpen: true), "<", reader);
}

/// <summary>
Expand Down
30 changes: 0 additions & 30 deletions MedallionShell/Helpers.cs
Expand Up @@ -15,36 +15,6 @@ public static T As<T>(this T @this)
{
return @this;
}

public static TResult GetResultWithUnwrappedException<TResult>(this Task<TResult> @this)
{
if (@this.IsCompleted)
{
return @this.Status == TaskStatus.RanToCompletion
? @this.Result
: throw ThrowUnwrapped(@this);
}

try { return @this.Result; }
catch (AggregateException)
{
throw ThrowUnwrapped(@this);
}
}

private static Exception ThrowUnwrapped(Task task)
{
if (task.IsFaulted)
{
ExceptionDispatchInfo.Capture(task.Exception.GetBaseException()).Throw();
}
else if (task.IsCanceled)
{
throw new TaskCanceledException();
}

return null;
}
}

internal static class Throw
Expand Down
11 changes: 9 additions & 2 deletions MedallionShell/IoCommand.cs
Expand Up @@ -10,11 +10,16 @@ internal sealed class IoCommand : Command
{
private readonly Command command;
private readonly Task<CommandResult> task;

public IoCommand(Command command, Task ioTask)
// for toString
private readonly string @operator;
private readonly object sourceOrSink;

public IoCommand(Command command, Task ioTask, string @operator, object sourceOrSink)
{
this.command = command;
this.task = this.CreateTask(ioTask);
this.@operator = @operator;
this.sourceOrSink = sourceOrSink;
}

private async Task<CommandResult> CreateTask(Task ioTask)
Expand Down Expand Up @@ -61,6 +66,8 @@ public override void Kill()
this.command.Kill();
}

public override string ToString() => $"{this.command} {this.@operator} {this.sourceOrSink}";

protected override void DisposeInternal()
{
this.command.As<IDisposable>().Dispose();
Expand Down
6 changes: 3 additions & 3 deletions MedallionShell/MedallionShell.csproj
Expand Up @@ -6,7 +6,7 @@

<PropertyGroup>
<RootNamespace>Medallion.Shell</RootNamespace>
<Version>1.4.0</Version>
<Version>1.5.0</Version>
<Authors>Michael Adelson</Authors>
<Description>A lightweight library that simplifies working with processes in .NET</Description>
<Copyright>Copyright © 2017 Michael Adelson</Copyright>
Expand All @@ -16,7 +16,7 @@
<PackageProjectUrl>https://github.com/madelson/MedallionShell</PackageProjectUrl>
<RepositoryUrl />
<FileVersion>1.0.0.0</FileVersion>
<PackageReleaseNotes>Added cancellation support, added API for getting the underlying process ID for a command even with the DisposeOnExit option, added API for consuming standard out and standard error lines together as a single stream, and improved Mono compatibility</PackageReleaseNotes>
<PackageReleaseNotes>Added ToString() override for Commands to simplify debugging. WindowsCommandLineSyntax no longer quotes arguments unnecessarily.</PackageReleaseNotes>
</PropertyGroup>

<PropertyGroup>
Expand All @@ -25,7 +25,7 @@
<WarningLevel>4</WarningLevel>
<DefineConstants>TRACE;DEBUG</DefineConstants>
<Optimize>True</Optimize>
<AssemblyVersion>1.4.0.0</AssemblyVersion>
<AssemblyVersion>1.5.0.0</AssemblyVersion>
</PropertyGroup>

<PropertyGroup>
Expand Down
2 changes: 2 additions & 0 deletions MedallionShell/PipedCommand.cs
Expand Up @@ -74,6 +74,8 @@ public override void Kill()
this.second.Kill();
}

public override string ToString() => this.first + " | " + this.second;

protected override void DisposeInternal()
{
this.first.As<IDisposable>().Dispose();
Expand Down
10 changes: 9 additions & 1 deletion MedallionShell/ProcessCommand.cs
Expand Up @@ -16,16 +16,22 @@ namespace Medallion.Shell
internal sealed class ProcessCommand : Command
{
private readonly bool disposeOnExit;
/// <summary>
/// Used for <see cref="ToString"/>
/// </summary>
private readonly string fileName, arguments;

internal ProcessCommand(
ProcessStartInfo startInfo,
bool throwOnError,
bool disposeOnExit,
bool disposeOnExit,
TimeSpan timeout,
CancellationToken cancellationToken,
Encoding standardInputEncoding)
{
this.disposeOnExit = disposeOnExit;
this.fileName = startInfo.FileName;
this.arguments = startInfo.Arguments;
this.process = new Process { StartInfo = startInfo, EnableRaisingEvents = true };

var processMonitoringTask = CreateProcessMonitoringTask(this.process);
Expand Down Expand Up @@ -169,6 +175,8 @@ public override Streams.ProcessStreamReader StandardError
private readonly Task<CommandResult> task;
public override Task<CommandResult> Task { get { return this.task; } }

public override string ToString() => this.fileName + " " + this.arguments;

public override void Kill()
{
this.ThrowIfDisposed();
Expand Down
5 changes: 2 additions & 3 deletions MedallionShell/Shell.cs
Expand Up @@ -59,7 +59,7 @@ public Command Run(string executable, IEnumerable<object> arguments = null, Acti
finalOptions.StartInfoInitializers.ForEach(a => a(processStartInfo));

var command = new ProcessCommand(
processStartInfo,
processStartInfo,
throwOnError: finalOptions.ThrowExceptionOnError,
disposeOnExit: finalOptions.DisposeProcessOnExit,
timeout: finalOptions.ProcessTimeout,
Expand All @@ -81,8 +81,7 @@ public Command Run(string executable, params object[] arguments)
return this.Run(executable, arguments.AsEnumerable());
}
#endregion

// TODO do we want to support static Run-type methods here as well?

#region ---- Static API ----
private static readonly Shell DefaultShell = new Shell();
/// <summary>
Expand Down
6 changes: 3 additions & 3 deletions MedallionShell/Streams/MergedLinesEnumerable.cs
Expand Up @@ -55,11 +55,11 @@ private IEnumerator<string> GetEnumeratorInternal()
}
else
{
var nextCompleted = Task.WhenAny(tasks.Select(t => t.Task)).GetResultWithUnwrappedException();
var nextCompleted = Task.WhenAny(tasks.Select(t => t.Task)).GetAwaiter().GetResult();
next = tasks[0].Task == nextCompleted ? tasks[0] : tasks[1];
}

var nextLine = next.Task.GetResultWithUnwrappedException();
var nextLine = next.Task.GetAwaiter().GetResult();
tasks.Remove(next);

if (nextLine != null)
Expand All @@ -69,7 +69,7 @@ private IEnumerator<string> GetEnumeratorInternal()
}
else
{
var otherAsyncLine = tasks[0].Task.GetResultWithUnwrappedException();
var otherAsyncLine = tasks[0].Task.GetAwaiter().GetResult();
if (otherAsyncLine != null)
{
yield return otherAsyncLine;
Expand Down

0 comments on commit ac89d81

Please sign in to comment.