File: BuiltInCommand.cs
Web Access
Project: ..\..\..\src\Cli\Microsoft.DotNet.Cli.Utils\Microsoft.DotNet.Cli.Utils.csproj (Microsoft.DotNet.Cli.Utils)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
 
namespace Microsoft.DotNet.Cli.Utils;
 
/// <summary>
/// A Command that is capable of running in the current process.
/// </summary>
public class BuiltInCommand : ICommand
{
    private readonly IEnumerable<string> _commandArgs;
    private readonly Func<string[], int> _builtInCommand;
    private readonly IBuiltInCommandEnvironment _environment;
    private readonly StreamForwarder _stdOut;
    private readonly StreamForwarder _stdErr;
    private string? _workingDirectory;
 
    public string CommandName { get; }
    public string CommandArgs => string.Join(" ", _commandArgs);
 
    public BuiltInCommand(string commandName, IEnumerable<string> commandArgs, Func<string[], int> builtInCommand)
        : this(commandName, commandArgs, builtInCommand, new BuiltInCommandEnvironment())
    {
    }
 
    internal BuiltInCommand(string commandName, IEnumerable<string> commandArgs, Func<string[], int> builtInCommand, IBuiltInCommandEnvironment environment)
    {
        CommandName = commandName;
        _commandArgs = commandArgs;
        _builtInCommand = builtInCommand;
        _environment = environment;
 
        _stdOut = new StreamForwarder();
        _stdErr = new StreamForwarder();
    }
 
    public CommandResult Execute()
    {
        TextWriter originalConsoleOut = _environment.GetConsoleOut();
        TextWriter originalConsoleError = _environment.GetConsoleError();
        string originalWorkingDirectory = _environment.GetWorkingDirectory();
 
        try
        {
            // redirecting the standard out and error so we can forward
            // the output to the caller
            using (BlockingMemoryStream outStream = new())
            using (BlockingMemoryStream errorStream = new())
            {
                _environment.SetConsoleOut(new StreamWriter(outStream) { AutoFlush = true });
                _environment.SetConsoleError(new StreamWriter(errorStream) { AutoFlush = true });
 
                // Reset the Reporters to the new Console Out and Error.
                Reporter.Reset();
 
                if (_workingDirectory is not null && _workingDirectory.Length != 0)
                {
                    _environment.SetWorkingDirectory(_workingDirectory);
                }
 
                var taskOut = _stdOut.BeginRead(new StreamReader(outStream));
                var taskErr = _stdErr.BeginRead(new StreamReader(errorStream));
 
                int exitCode = _builtInCommand([.. _commandArgs]);
 
                outStream.DoneWriting();
                errorStream.DoneWriting();
 
                Task.WaitAll(taskOut, taskErr);
 
                // fake out a ProcessStartInfo using the Muxer command name, since this is a built-in command
                ProcessStartInfo startInfo = new(new Muxer().MuxerPath, $"{CommandName} {CommandArgs}");
                return new CommandResult(startInfo, exitCode, null, null);
            }
        }
        finally
        {
            _environment.SetConsoleOut(originalConsoleOut);
            _environment.SetConsoleError(originalConsoleError);
            _environment.SetWorkingDirectory(originalWorkingDirectory);
 
            Reporter.Reset();
        }
    }
 
    public ICommand OnOutputLine(Action<string> handler)
    {
        if (handler == null)
        {
            throw new ArgumentNullException(nameof(handler));
        }
 
        _stdOut.ForwardTo(writeLine: handler);
 
        return this;
    }
 
    public ICommand OnErrorLine(Action<string> handler)
    {
        if (handler == null)
        {
            throw new ArgumentNullException(nameof(handler));
        }
 
        _stdErr.ForwardTo(writeLine: handler);
 
        return this;
    }
 
    public ICommand WorkingDirectory(string workingDirectory)
    {
        _workingDirectory = workingDirectory;
 
        return this;
    }
 
    private class BuiltInCommandEnvironment : IBuiltInCommandEnvironment
    {
        public TextWriter GetConsoleOut()
        {
            return Console.Out;
        }
 
        public void SetConsoleOut(TextWriter newOut)
        {
            Console.SetOut(newOut);
        }
 
        public TextWriter GetConsoleError()
        {
            return Console.Error;
        }
 
        public void SetConsoleError(TextWriter newError)
        {
            Console.SetError(newError);
        }
 
        public string GetWorkingDirectory()
        {
            return Directory.GetCurrentDirectory();
        }
 
        public void SetWorkingDirectory(string path)
        {
            Directory.SetCurrentDirectory(path);
        }
    }
 
    public ICommand CaptureStdErr()
    {
        _stdErr.Capture();
 
        return this;
    }
 
    public ICommand CaptureStdOut()
    {
        _stdOut.Capture();
 
        return this;
    }
 
    public ICommand EnvironmentVariable(string name, string? value)
    {
        throw new NotImplementedException();
    }
 
    public ICommand ForwardStdErr(TextWriter? to = null, bool onlyIfVerbose = false, bool ansiPassThrough = true)
    {
        throw new NotImplementedException();
    }
 
    public ICommand ForwardStdOut(TextWriter? to = null, bool onlyIfVerbose = false, bool ansiPassThrough = true)
    {
        throw new NotImplementedException();
    }
    public ICommand SetCommandArgs(string commandArgs)
    {
        throw new NotImplementedException();
    }
 
    public ICommand StandardOutputEncoding(Encoding encoding)
    {
        throw new NotImplementedException();
    }
}