File: Invocation\InvocationPipeline.cs
Web Access
Project: src\src\command-line-api\src\System.CommandLine\System.CommandLine.csproj (System.CommandLine)
// 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.Threading;
using System.Threading.Tasks;

namespace System.CommandLine.Invocation
{
    internal static class InvocationPipeline
    {
        internal static async Task<int> InvokeAsync(ParseResult parseResult, CancellationToken cancellationToken)
        {
            ProcessTerminationHandler? terminationHandler = null;
            using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);

            try
            {
                int actionResult = 0;
                int preActionResult = 0;

                if (parseResult.PreActions is not null)
                {
                    for (int i = 0; i < parseResult.PreActions.Count; i++)
                    {
                        var action = parseResult.PreActions[i];
                        var result = 0;

                        switch (action)
                        {
                            case SynchronousCommandLineAction syncAction:
                                result = syncAction.Invoke(parseResult);
                                break;
                            case AsynchronousCommandLineAction asyncAction:
                                result = await asyncAction.InvokeAsync(parseResult, cts.Token);
                                break;
                           
                        }

                        if (result != 0)
                        {
                            preActionResult = result;
                        }
                    }
                }

                if (parseResult.Action is null)
                {
                    return preActionResult != 0 ? preActionResult : ReturnCodeForMissingAction(parseResult);
                }

                switch (parseResult.Action)
                {
                    case SynchronousCommandLineAction syncAction:
                        actionResult = syncAction.Invoke(parseResult);
                        break;

                    case AsynchronousCommandLineAction asyncAction:
                        var timeout = parseResult.InvocationConfiguration.ProcessTerminationTimeout;

                        if (timeout.HasValue)
                        {
                            terminationHandler = new(cts, timeout.Value);
                        }

                        var startedInvocation = asyncAction.InvokeAsync(parseResult, cts.Token);

                        if (terminationHandler is null)
                        {
                            actionResult = await startedInvocation;
                        }
                        else
                        {
                            terminationHandler.StartedHandler = startedInvocation;
                            // Handlers may not implement cancellation.
                            // In such cases, when CancelOnProcessTermination is configured and user presses Ctrl+C,
                            // ProcessTerminationCompletionSource completes first, with the result equal to native exit code for given signal.
                            Task<int> firstCompletedTask = await Task.WhenAny(startedInvocation, terminationHandler.ProcessTerminationCompletionSource.Task);
                            actionResult = await firstCompletedTask; // return the result or propagate the exception
                        }
                        break;

                    default:
                        throw new ArgumentOutOfRangeException(nameof(parseResult.Action));
                }

                return preActionResult != 0 ? preActionResult : actionResult;
            }
            catch (Exception ex) when (parseResult.InvocationConfiguration.EnableDefaultExceptionHandler)
            {
                return DefaultExceptionHandler(ex, parseResult);
            }
            finally
            {
                terminationHandler?.Dispose();
            }
        }

        internal static int Invoke(ParseResult parseResult)
        {
            try
            {
                int preActionResult = 0;

                if (parseResult.PreActions is not null)
                {
                    for (var i = 0; i < parseResult.PreActions.Count; i++)
                    {
                        if (parseResult.PreActions[i] is SynchronousCommandLineAction syncPreAction)
                        {
                            int result = syncPreAction.Invoke(parseResult);
                            
                            if (result != 0)
                            {
                                preActionResult = result;
                            }
                        }
                    }
                }

                switch (parseResult.Action)
                {
                    case null:
                        return preActionResult != 0 ? preActionResult : ReturnCodeForMissingAction(parseResult);

                    case SynchronousCommandLineAction syncAction:
                        int actionResult = syncAction.Invoke(parseResult);
                        return preActionResult != 0 ? preActionResult : actionResult;

                    default:
                        throw new InvalidOperationException($"{nameof(AsynchronousCommandLineAction)} called within non-async invocation.");
                }
            }
            catch (Exception ex) when (parseResult.InvocationConfiguration.EnableDefaultExceptionHandler)
            {
                return DefaultExceptionHandler(ex, parseResult);
            }
        }

        private static int DefaultExceptionHandler(Exception exception, ParseResult parseResult)
        {
            if (exception is not OperationCanceledException)
            {
                ConsoleHelpers.ResetTerminalForegroundColor();
                ConsoleHelpers.SetTerminalForegroundRed();

                var error = parseResult.InvocationConfiguration.Error;

                error.Write(LocalizationResources.ExceptionHandlerHeader());
                error.WriteLine(exception.ToString());

                ConsoleHelpers.ResetTerminalForegroundColor();
            }
            return 1;
        }

        private static int ReturnCodeForMissingAction(ParseResult parseResult)
        {
            if (parseResult.Errors.Count > 0)
            {
                return 1;
            }
            else
            {
                return 0;
            }
        }
    }
}