2015-11-16 11:21:57 -08:00
|
|
|
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
|
|
|
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
|
|
|
|
|
|
|
|
using System;
|
2015-10-06 10:46:43 -07:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Runtime.CompilerServices;
|
2015-10-13 14:31:29 -07:00
|
|
|
|
using System.Runtime.InteropServices;
|
2015-12-07 14:18:09 -08:00
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Threading;
|
2015-11-28 00:28:45 -08:00
|
|
|
|
using Microsoft.DotNet.ProjectModel;
|
|
|
|
|
using NuGet.Frameworks;
|
2015-10-06 10:46:43 -07:00
|
|
|
|
|
|
|
|
|
namespace Microsoft.DotNet.Cli.Utils
|
|
|
|
|
{
|
2015-12-14 08:33:11 -08:00
|
|
|
|
internal class Command
|
2015-10-06 10:46:43 -07:00
|
|
|
|
{
|
2015-12-07 14:18:09 -08:00
|
|
|
|
private readonly Process _process;
|
|
|
|
|
private readonly StreamForwarder _stdOut;
|
|
|
|
|
private readonly StreamForwarder _stdErr;
|
2015-10-06 10:46:43 -07:00
|
|
|
|
|
2015-12-14 15:44:58 -08:00
|
|
|
|
public enum CommandResolutionStrategy
|
|
|
|
|
{
|
|
|
|
|
//command loaded from a nuget package
|
|
|
|
|
NugetPackage,
|
|
|
|
|
|
|
|
|
|
//command loaded from the same directory as the executing assembly
|
|
|
|
|
BaseDirectory,
|
|
|
|
|
|
|
|
|
|
//command loaded from path
|
|
|
|
|
Path,
|
|
|
|
|
|
|
|
|
|
//command not found
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-06 10:46:43 -07:00
|
|
|
|
private bool _running = false;
|
|
|
|
|
|
2015-12-10 13:06:33 -08:00
|
|
|
|
private Command(string executable, string args, CommandResolutionStrategy resolutionStrategy)
|
2015-10-06 10:46:43 -07:00
|
|
|
|
{
|
|
|
|
|
// Set the things we need
|
|
|
|
|
var psi = new ProcessStartInfo()
|
|
|
|
|
{
|
|
|
|
|
FileName = executable,
|
|
|
|
|
Arguments = args,
|
|
|
|
|
RedirectStandardError = true,
|
|
|
|
|
RedirectStandardOutput = true
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
_process = new Process()
|
|
|
|
|
{
|
|
|
|
|
StartInfo = psi
|
|
|
|
|
};
|
2015-12-07 14:18:09 -08:00
|
|
|
|
|
|
|
|
|
_stdOut = new StreamForwarder();
|
|
|
|
|
_stdErr = new StreamForwarder();
|
2015-12-14 15:44:58 -08:00
|
|
|
|
|
2015-12-10 13:06:33 -08:00
|
|
|
|
ResolutionStrategy = resolutionStrategy;
|
2015-10-06 10:46:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
2015-11-28 00:28:45 -08:00
|
|
|
|
public static Command Create(string executable, IEnumerable<string> args, NuGetFramework framework = null)
|
2015-10-06 10:46:43 -07:00
|
|
|
|
{
|
2015-11-28 00:28:45 -08:00
|
|
|
|
return Create(executable, string.Join(" ", args), framework);
|
2015-10-06 10:46:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
2015-11-28 00:28:45 -08:00
|
|
|
|
public static Command Create(string executable, string args, NuGetFramework framework = null)
|
2015-10-06 10:46:43 -07:00
|
|
|
|
{
|
2015-11-15 17:18:32 -08:00
|
|
|
|
|
2015-12-10 13:06:33 -08:00
|
|
|
|
var resolutionStrategy = CommandResolutionStrategy.None;
|
2015-12-14 15:44:58 -08:00
|
|
|
|
|
2015-12-10 13:06:33 -08:00
|
|
|
|
ResolveExecutablePath(ref executable, ref args, ref resolutionStrategy, framework);
|
2015-12-14 15:44:58 -08:00
|
|
|
|
|
2015-12-10 13:06:33 -08:00
|
|
|
|
return new Command(executable, args, resolutionStrategy);
|
2015-11-15 17:18:32 -08:00
|
|
|
|
}
|
|
|
|
|
|
2015-12-10 13:06:33 -08:00
|
|
|
|
private static void ResolveExecutablePath(ref string executable, ref string args, ref CommandResolutionStrategy resolutionStrategy, NuGetFramework framework = null)
|
2015-11-28 00:28:45 -08:00
|
|
|
|
{
|
|
|
|
|
executable =
|
2015-12-10 13:06:33 -08:00
|
|
|
|
ResolveExecutablePathFromProject(executable, framework, ref resolutionStrategy) ??
|
|
|
|
|
ResolveExecutableFromPath(executable, ref args, ref resolutionStrategy);
|
2015-11-28 00:28:45 -08:00
|
|
|
|
}
|
|
|
|
|
|
2015-12-10 13:06:33 -08:00
|
|
|
|
private static string ResolveExecutableFromPath(string executable, ref string args, ref CommandResolutionStrategy resolutionStrategy)
|
2015-11-15 17:18:32 -08:00
|
|
|
|
{
|
2015-12-10 13:06:33 -08:00
|
|
|
|
resolutionStrategy = CommandResolutionStrategy.Path;
|
2015-12-14 15:44:58 -08:00
|
|
|
|
|
2015-11-25 00:24:28 -08:00
|
|
|
|
foreach (string suffix in Constants.RunnableSuffixes)
|
2015-11-15 17:18:32 -08:00
|
|
|
|
{
|
2015-11-25 00:24:28 -08:00
|
|
|
|
var fullExecutable = Path.GetFullPath(Path.Combine(
|
|
|
|
|
AppContext.BaseDirectory, executable + suffix));
|
|
|
|
|
|
|
|
|
|
if (File.Exists(fullExecutable))
|
|
|
|
|
{
|
|
|
|
|
executable = fullExecutable;
|
2015-12-14 15:44:58 -08:00
|
|
|
|
|
2015-12-10 13:06:33 -08:00
|
|
|
|
resolutionStrategy = CommandResolutionStrategy.BaseDirectory;
|
2015-11-25 00:24:28 -08:00
|
|
|
|
|
|
|
|
|
// In priority order we've found the best runnable extension, so break.
|
|
|
|
|
break;
|
|
|
|
|
}
|
2015-11-15 17:18:32 -08:00
|
|
|
|
}
|
|
|
|
|
|
2015-10-15 12:18:45 -07:00
|
|
|
|
// On Windows, we want to avoid using "cmd" if possible (it mangles the colors, and a bunch of other things)
|
|
|
|
|
// So, do a quick path search to see if we can just directly invoke it
|
|
|
|
|
var useCmd = ShouldUseCmd(executable);
|
|
|
|
|
|
|
|
|
|
if (useCmd)
|
2015-10-06 10:46:43 -07:00
|
|
|
|
{
|
2015-10-15 12:18:45 -07:00
|
|
|
|
var comSpec = Environment.GetEnvironmentVariable("ComSpec");
|
2015-12-04 16:26:07 -08:00
|
|
|
|
// wrap 'executable' within quotes to deal woth space in its path.
|
|
|
|
|
args = $"/S /C \"\"{executable}\" {args}\"";
|
2015-10-06 10:46:43 -07:00
|
|
|
|
executable = comSpec;
|
|
|
|
|
}
|
2015-11-28 00:28:45 -08:00
|
|
|
|
|
|
|
|
|
return executable;
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-10 13:06:33 -08:00
|
|
|
|
private static string ResolveExecutablePathFromProject(string executable, NuGetFramework framework, ref CommandResolutionStrategy resolutionStrategy)
|
2015-11-28 00:28:45 -08:00
|
|
|
|
{
|
|
|
|
|
if (framework == null) return null;
|
|
|
|
|
|
2015-11-29 10:58:13 -08:00
|
|
|
|
var projectRootPath = Directory.GetCurrentDirectory();
|
2015-11-28 00:28:45 -08:00
|
|
|
|
|
2015-11-29 10:58:13 -08:00
|
|
|
|
if (!File.Exists(Path.Combine(projectRootPath, Project.FileName))) return null;
|
2015-11-28 00:28:45 -08:00
|
|
|
|
|
|
|
|
|
var commandName = Path.GetFileNameWithoutExtension(executable);
|
|
|
|
|
|
|
|
|
|
var projectContext = ProjectContext.Create(projectRootPath, framework);
|
|
|
|
|
|
|
|
|
|
var commandPackage = projectContext.LibraryManager.GetLibraries()
|
|
|
|
|
.Where(l => l.GetType() == typeof (PackageDescription))
|
|
|
|
|
.Select(l => l as PackageDescription)
|
|
|
|
|
.FirstOrDefault(p =>
|
|
|
|
|
{
|
|
|
|
|
var fileNames = p.Library.Files
|
|
|
|
|
.Select(Path.GetFileName)
|
|
|
|
|
.Where(n => Path.GetFileNameWithoutExtension(n) == commandName)
|
|
|
|
|
.ToList();
|
|
|
|
|
|
2015-11-29 10:58:13 -08:00
|
|
|
|
return fileNames.Contains(commandName + FileNameSuffixes.DotNet.Exe) &&
|
|
|
|
|
fileNames.Contains(commandName + FileNameSuffixes.DotNet.DynamicLib) &&
|
|
|
|
|
fileNames.Contains(commandName + FileNameSuffixes.DotNet.Deps);
|
2015-11-28 00:28:45 -08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (commandPackage == null) return null;
|
|
|
|
|
|
|
|
|
|
var commandPath = commandPackage.Library.Files
|
2015-11-29 10:58:13 -08:00
|
|
|
|
.First(f => Path.GetFileName(f) == commandName + FileNameSuffixes.DotNet.Exe);
|
2015-11-28 00:28:45 -08:00
|
|
|
|
|
2015-12-10 13:06:33 -08:00
|
|
|
|
resolutionStrategy = CommandResolutionStrategy.NugetPackage;
|
2015-12-14 15:44:58 -08:00
|
|
|
|
|
2015-11-28 00:28:45 -08:00
|
|
|
|
return Path.Combine(projectContext.PackagesDirectory, commandPackage.Path, commandPath);
|
2015-10-15 12:18:45 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static bool ShouldUseCmd(string executable)
|
|
|
|
|
{
|
|
|
|
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
2015-10-06 10:46:43 -07:00
|
|
|
|
{
|
2015-10-15 12:18:45 -07:00
|
|
|
|
var extension = Path.GetExtension(executable);
|
2015-10-16 04:21:40 -07:00
|
|
|
|
if (!string.IsNullOrEmpty(extension))
|
2015-10-15 12:18:45 -07:00
|
|
|
|
{
|
2015-10-16 04:21:40 -07:00
|
|
|
|
return !string.Equals(extension, ".exe", StringComparison.Ordinal);
|
2015-10-15 12:18:45 -07:00
|
|
|
|
}
|
|
|
|
|
else if (executable.Contains(Path.DirectorySeparatorChar))
|
|
|
|
|
{
|
|
|
|
|
// It's a relative path without an extension
|
|
|
|
|
if (File.Exists(executable + ".exe"))
|
|
|
|
|
{
|
|
|
|
|
// It refers to an exe!
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Search the path to see if we can find it
|
|
|
|
|
foreach (var path in Environment.GetEnvironmentVariable("PATH").Split(Path.PathSeparator))
|
|
|
|
|
{
|
|
|
|
|
var candidate = Path.Combine(path, executable + ".exe");
|
|
|
|
|
if (File.Exists(candidate))
|
|
|
|
|
{
|
|
|
|
|
// We found an exe!
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// It's a non-exe :(
|
|
|
|
|
return true;
|
2015-10-06 10:46:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
2015-10-15 12:18:45 -07:00
|
|
|
|
// Non-windows never uses cmd
|
|
|
|
|
return false;
|
2015-10-06 10:46:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
2015-10-21 03:11:27 -07:00
|
|
|
|
public CommandResult Execute()
|
2015-10-06 10:46:43 -07:00
|
|
|
|
{
|
2015-11-20 19:00:56 -08:00
|
|
|
|
Reporter.Verbose.WriteLine($"Running {_process.StartInfo.FileName} {_process.StartInfo.Arguments}");
|
|
|
|
|
|
2015-10-06 10:46:43 -07:00
|
|
|
|
ThrowIfRunning();
|
|
|
|
|
_running = true;
|
|
|
|
|
|
|
|
|
|
_process.EnableRaisingEvents = true;
|
|
|
|
|
|
|
|
|
|
#if DEBUG
|
2015-10-18 21:24:26 -07:00
|
|
|
|
var sw = Stopwatch.StartNew();
|
2015-11-01 16:21:10 -08:00
|
|
|
|
Reporter.Verbose.WriteLine($"> {FormatProcessInfo(_process.StartInfo)}".White());
|
2015-10-06 10:46:43 -07:00
|
|
|
|
#endif
|
|
|
|
|
_process.Start();
|
2015-11-29 10:58:13 -08:00
|
|
|
|
|
|
|
|
|
Reporter.Verbose.WriteLine($"Process ID: {_process.Id}");
|
|
|
|
|
|
2015-12-07 14:18:09 -08:00
|
|
|
|
var threadOut = _stdOut.BeginRead(_process.StandardOutput);
|
|
|
|
|
var threadErr = _stdErr.BeginRead(_process.StandardError);
|
2015-10-06 10:46:43 -07:00
|
|
|
|
|
2015-10-21 03:11:27 -07:00
|
|
|
|
_process.WaitForExit();
|
2015-12-07 14:18:09 -08:00
|
|
|
|
threadOut.Join();
|
|
|
|
|
threadErr.Join();
|
2015-10-21 03:11:27 -07:00
|
|
|
|
|
|
|
|
|
var exitCode = _process.ExitCode;
|
2015-10-06 10:46:43 -07:00
|
|
|
|
|
2015-10-18 19:02:09 -07:00
|
|
|
|
#if DEBUG
|
2015-10-22 05:31:24 -07:00
|
|
|
|
var message = $"< {FormatProcessInfo(_process.StartInfo)} exited with {exitCode} in {sw.ElapsedMilliseconds} ms.";
|
2015-10-20 02:42:29 -07:00
|
|
|
|
if (exitCode == 0)
|
|
|
|
|
{
|
2015-11-01 16:21:10 -08:00
|
|
|
|
Reporter.Verbose.WriteLine(message.Green());
|
2015-10-20 02:42:29 -07:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2015-11-01 16:21:10 -08:00
|
|
|
|
Reporter.Verbose.WriteLine(message.Red().Bold());
|
2015-10-20 02:42:29 -07:00
|
|
|
|
}
|
2015-10-18 19:02:09 -07:00
|
|
|
|
#endif
|
|
|
|
|
|
2015-10-06 10:46:43 -07:00
|
|
|
|
return new CommandResult(
|
|
|
|
|
exitCode,
|
2015-12-07 14:18:09 -08:00
|
|
|
|
_stdOut.GetCapturedOutput(),
|
|
|
|
|
_stdErr.GetCapturedOutput());
|
2015-10-06 10:46:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
2015-11-17 18:02:08 -08:00
|
|
|
|
public Command WorkingDirectory(string projectDirectory)
|
|
|
|
|
{
|
|
|
|
|
_process.StartInfo.WorkingDirectory = projectDirectory;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-30 15:40:13 -07:00
|
|
|
|
public Command EnvironmentVariable(string name, string value)
|
|
|
|
|
{
|
2015-11-01 06:24:31 -08:00
|
|
|
|
_process.StartInfo.Environment[name] = value;
|
2015-10-30 15:40:13 -07:00
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-06 10:46:43 -07:00
|
|
|
|
public Command CaptureStdOut()
|
|
|
|
|
{
|
|
|
|
|
ThrowIfRunning();
|
2015-12-07 14:18:09 -08:00
|
|
|
|
_stdOut.Capture();
|
2015-10-06 10:46:43 -07:00
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Command CaptureStdErr()
|
|
|
|
|
{
|
|
|
|
|
ThrowIfRunning();
|
2015-12-07 14:18:09 -08:00
|
|
|
|
_stdErr.Capture();
|
2015-10-06 10:46:43 -07:00
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-01 16:21:10 -08:00
|
|
|
|
public Command ForwardStdOut(TextWriter to = null, bool onlyIfVerbose = false)
|
2015-10-06 10:46:43 -07:00
|
|
|
|
{
|
|
|
|
|
ThrowIfRunning();
|
2015-11-01 16:21:10 -08:00
|
|
|
|
if (!onlyIfVerbose || CommandContext.IsVerbose())
|
|
|
|
|
{
|
|
|
|
|
if (to == null)
|
|
|
|
|
{
|
2015-12-07 14:18:09 -08:00
|
|
|
|
_stdOut.ForwardTo(write: Reporter.Output.Write, writeLine: Reporter.Output.WriteLine);
|
2015-11-01 16:21:10 -08:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2015-12-07 14:18:09 -08:00
|
|
|
|
_stdOut.ForwardTo(write: to.Write, writeLine: to.WriteLine);
|
2015-11-01 16:21:10 -08:00
|
|
|
|
}
|
|
|
|
|
}
|
2015-10-06 10:46:43 -07:00
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-01 16:21:10 -08:00
|
|
|
|
public Command ForwardStdErr(TextWriter to = null, bool onlyIfVerbose = false)
|
2015-10-06 10:46:43 -07:00
|
|
|
|
{
|
|
|
|
|
ThrowIfRunning();
|
2015-11-01 16:21:10 -08:00
|
|
|
|
if (!onlyIfVerbose || CommandContext.IsVerbose())
|
|
|
|
|
{
|
|
|
|
|
if (to == null)
|
|
|
|
|
{
|
2015-12-07 14:18:09 -08:00
|
|
|
|
_stdErr.ForwardTo(write: Reporter.Error.Write, writeLine: Reporter.Error.WriteLine);
|
2015-11-01 16:21:10 -08:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2015-12-07 14:18:09 -08:00
|
|
|
|
_stdErr.ForwardTo(write: to.Write, writeLine: to.WriteLine);
|
2015-11-01 16:21:10 -08:00
|
|
|
|
}
|
|
|
|
|
}
|
2015-10-06 10:46:43 -07:00
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Command OnOutputLine(Action<string> handler)
|
|
|
|
|
{
|
|
|
|
|
ThrowIfRunning();
|
2015-12-07 14:18:09 -08:00
|
|
|
|
_stdOut.ForwardTo(write: null, writeLine: handler);
|
2015-10-06 10:46:43 -07:00
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Command OnErrorLine(Action<string> handler)
|
|
|
|
|
{
|
|
|
|
|
ThrowIfRunning();
|
2015-12-07 14:18:09 -08:00
|
|
|
|
_stdErr.ForwardTo(write: null, writeLine: handler);
|
2015-10-06 10:46:43 -07:00
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-10 13:06:33 -08:00
|
|
|
|
public CommandResolutionStrategy ResolutionStrategy { get; }
|
|
|
|
|
|
|
|
|
|
public string CommandName => _process.StartInfo.FileName;
|
2015-12-14 15:44:58 -08:00
|
|
|
|
|
2015-10-21 03:11:27 -07:00
|
|
|
|
private string FormatProcessInfo(ProcessStartInfo info)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(info.Arguments))
|
|
|
|
|
{
|
|
|
|
|
return info.FileName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return info.FileName + " " + info.Arguments;
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-06 10:46:43 -07:00
|
|
|
|
private void ThrowIfRunning([CallerMemberName] string memberName = null)
|
|
|
|
|
{
|
|
|
|
|
if (_running)
|
|
|
|
|
{
|
|
|
|
|
throw new InvalidOperationException($"Unable to invoke {memberName} after the command has been run");
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-12-07 14:18:09 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal sealed class StreamForwarder
|
|
|
|
|
{
|
|
|
|
|
private const int DefaultBufferSize = 256;
|
|
|
|
|
|
|
|
|
|
private readonly int _bufferSize;
|
|
|
|
|
private StringBuilder _builder;
|
|
|
|
|
private StringWriter _capture;
|
|
|
|
|
private Action<string> _write;
|
|
|
|
|
private Action<string> _writeLine;
|
|
|
|
|
|
|
|
|
|
internal StreamForwarder(int bufferSize = DefaultBufferSize)
|
|
|
|
|
{
|
|
|
|
|
_bufferSize = bufferSize;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal void Capture()
|
|
|
|
|
{
|
|
|
|
|
if (_capture != null)
|
|
|
|
|
{
|
|
|
|
|
throw new InvalidOperationException("Already capturing stream!");
|
|
|
|
|
}
|
|
|
|
|
_capture = new StringWriter();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal string GetCapturedOutput()
|
|
|
|
|
{
|
|
|
|
|
return _capture?.GetStringBuilder()?.ToString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal void ForwardTo(Action<string> write, Action<string> writeLine)
|
|
|
|
|
{
|
|
|
|
|
if (writeLine == null)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentNullException(nameof(writeLine));
|
|
|
|
|
}
|
|
|
|
|
if (_writeLine != null)
|
|
|
|
|
{
|
|
|
|
|
throw new InvalidOperationException("Already handling stream!");
|
|
|
|
|
}
|
|
|
|
|
_write = write;
|
|
|
|
|
_writeLine = writeLine;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal Thread BeginRead(TextReader reader)
|
|
|
|
|
{
|
|
|
|
|
var thread = new Thread(() => Read(reader)) { IsBackground = true };
|
|
|
|
|
thread.Start();
|
|
|
|
|
return thread;
|
|
|
|
|
}
|
2015-10-06 10:46:43 -07:00
|
|
|
|
|
2015-12-07 14:18:09 -08:00
|
|
|
|
internal void Read(TextReader reader)
|
2015-10-06 10:46:43 -07:00
|
|
|
|
{
|
2015-12-07 14:18:09 -08:00
|
|
|
|
_builder = new StringBuilder();
|
|
|
|
|
var buffer = new char[_bufferSize];
|
|
|
|
|
int n;
|
|
|
|
|
while ((n = reader.Read(buffer, 0, _bufferSize)) > 0)
|
|
|
|
|
{
|
|
|
|
|
_builder.Append(buffer, 0, n);
|
|
|
|
|
WriteBlocks();
|
|
|
|
|
}
|
|
|
|
|
WriteRemainder();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void WriteBlocks()
|
|
|
|
|
{
|
|
|
|
|
int n = _builder.Length;
|
|
|
|
|
if (n == 0)
|
2015-10-06 10:46:43 -07:00
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-07 14:18:09 -08:00
|
|
|
|
int offset = 0;
|
|
|
|
|
bool sawReturn = false;
|
|
|
|
|
for (int i = 0; i < n; i++)
|
|
|
|
|
{
|
|
|
|
|
char c = _builder[i];
|
|
|
|
|
switch (c)
|
|
|
|
|
{
|
|
|
|
|
case '\r':
|
|
|
|
|
sawReturn = true;
|
|
|
|
|
continue;
|
|
|
|
|
case '\n':
|
|
|
|
|
WriteLine(_builder.ToString(offset, i - offset - (sawReturn ? 1 : 0)));
|
|
|
|
|
offset = i + 1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
sawReturn = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the buffer contains no line breaks and _write is
|
|
|
|
|
// supported, send the buffer content.
|
|
|
|
|
if (!sawReturn &&
|
|
|
|
|
(offset == 0) &&
|
|
|
|
|
((_write != null) || (_writeLine == null)))
|
|
|
|
|
{
|
|
|
|
|
WriteRemainder();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_builder.Remove(0, offset);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void WriteRemainder()
|
|
|
|
|
{
|
|
|
|
|
if (_builder.Length == 0)
|
2015-10-06 10:46:43 -07:00
|
|
|
|
{
|
2015-12-07 14:18:09 -08:00
|
|
|
|
return;
|
2015-10-06 10:46:43 -07:00
|
|
|
|
}
|
2015-12-07 14:18:09 -08:00
|
|
|
|
Write(_builder.ToString());
|
|
|
|
|
_builder.Clear();
|
|
|
|
|
}
|
2015-10-06 10:46:43 -07:00
|
|
|
|
|
2015-12-07 14:18:09 -08:00
|
|
|
|
private void WriteLine(string str)
|
|
|
|
|
{
|
|
|
|
|
if (_capture != null)
|
2015-10-06 10:46:43 -07:00
|
|
|
|
{
|
2015-12-07 14:18:09 -08:00
|
|
|
|
_capture.WriteLine(str);
|
2015-10-06 10:46:43 -07:00
|
|
|
|
}
|
2015-12-07 14:18:09 -08:00
|
|
|
|
// If _write is supported, so is _writeLine.
|
|
|
|
|
if (_writeLine != null)
|
|
|
|
|
{
|
|
|
|
|
_writeLine(str);
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-10-06 10:46:43 -07:00
|
|
|
|
|
2015-12-07 14:18:09 -08:00
|
|
|
|
private void Write(string str)
|
|
|
|
|
{
|
|
|
|
|
if (_capture != null)
|
|
|
|
|
{
|
|
|
|
|
_capture.Write(str);
|
|
|
|
|
}
|
|
|
|
|
if (_write != null)
|
|
|
|
|
{
|
|
|
|
|
_write(str);
|
|
|
|
|
}
|
|
|
|
|
else if (_writeLine != null)
|
2015-10-06 10:46:43 -07:00
|
|
|
|
{
|
2015-12-07 14:18:09 -08:00
|
|
|
|
_writeLine(str);
|
2015-10-06 10:46:43 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|