File: ParserOptionActions.cs
Web Access
Project: src\src\sdk\src\Cli\dotnet\dotnet.csproj (dotnet)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.CommandLine;
using System.CommandLine.Invocation;
using Microsoft.DotNet.Cli.CommandLine;
using Microsoft.DotNet.Cli.Commands.Workload;
using Microsoft.DotNet.Cli.Extensions;
using Microsoft.DotNet.Cli.Help;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Configurer;
using RuntimeEnvironment = Microsoft.DotNet.Cli.Utils.RuntimeEnvironment;

namespace Microsoft.DotNet.Cli;

/// <summary>
/// Represents an option that contains an invocable action.
/// These are essentially commands that are only defined as an option.
/// </summary>
internal abstract class InvocableOptionAction(Option option) : SynchronousCommandLineAction
{
    /// <summary>
    /// The option for which this action is bound.
    /// </summary>
    public Option Option { get; } = option;
}

internal class HandleDiagnosticAction(Option<bool> option) : InvocableOptionAction(option)
{
    public override bool Terminating => false;

    // S.CL will always invoke this option action because:
    // 1. A boolean option has a default value of `false`.
    // 2. The option has Terminating set to `false`.
    // When a default value exists (non-null) and the option is not terminating, the option action is always invoked.
    public override int Invoke(ParseResult parseResult)
    {
        // This check is necessary as per comment above.
        if (!parseResult.HasOption(Option) || !parseResult.GetValue(option)
            // Only set verbose output on built-in commands.
            || !parseResult.IsDotnetBuiltInCommand())
        {
            return 0;
        }

        // Determine whether the diagnostic option should be attached to the dotnet command or the subcommand.
        if (OptionPrecedesSubcommand(parseResult.Tokens.Select(t => t.Value), parseResult.RootSubCommandResult()))
        {
            Environment.SetEnvironmentVariable(CommandLoggingContext.Variables.Verbose, bool.TrueString);
            CommandLoggingContext.SetVerbose(true);
            Reporter.Reset();

            var home = Env.GetEnvironmentVariable(CliFolderPathCalculator.DotnetHomeVariableName);
            if (!string.IsNullOrEmpty(home))
            {
                // Output DOTNET_CLI_HOME usage when verbosity is enabled.
                Reporter.Verbose.WriteLine(string.Format(LocalizableStrings.DotnetCliHomeUsed, home, CliFolderPathCalculator.DotnetHomeVariableName));
            }
        }

        return 0;
    }

    private bool OptionPrecedesSubcommand(IEnumerable<string> tokens, string subCommand)
    {
        if (string.IsNullOrEmpty(subCommand))
        {
            return true;
        }

        foreach (var token in tokens)
        {
            if (token == subCommand)
            {
                return false;
            }

            if (Option.Name == token || Option.Aliases.Contains(token))
            {
                return true;
            }
        }

        return false;
    }
}

internal class PrintHelpAction(Option option, HelpBuilder builder) : InvocableOptionAction(option)
{
    public override bool Terminating => true;
    public override bool ClearsParseErrors => true;

    private HelpBuilder Builder { get; } = builder;

    public override int Invoke(ParseResult parseResult)
    {
        var command = parseResult.CommandResult.Command;
        var output = parseResult.InvocationConfiguration.Output;
        var helpContext = new HelpContext(Builder, command, output, parseResult);
        Builder.Write(helpContext);

        return 0;
    }
}

internal class PrintVersionAction(Option<bool> option) : InvocableOptionAction(option)
{
    public override bool Terminating => true;

    public override int Invoke(ParseResult parseResult)
    {
        if (!parseResult.HasOption(Option) || !parseResult.GetValue(option)
            // Only print for top-level commands.
            || !parseResult.IsTopLevelDotnetCommand())
        {
            return 0;
        }

        Reporter.Output.WriteLine(Product.Version);

        return 0;
    }
}

internal class PrintInfoAction(Option<bool> option) : InvocableOptionAction(option)
{
    public override bool Terminating => true;

    public override int Invoke(ParseResult parseResult)
    {
        if (!parseResult.HasOption(Option) || !parseResult.GetValue(option)
            // Only print for top-level commands.
            || !parseResult.IsTopLevelDotnetCommand())
        {
            return 0;
        }

        DotnetVersionFile versionFile = DotnetFiles.VersionFileObject;
        var commitSha = versionFile.CommitSha ?? "N/A";
        Reporter.Output.WriteLine($"{LocalizableStrings.DotNetSdkInfoLabel}");
        Reporter.Output.WriteLine($" Version:           {Product.Version}");
        Reporter.Output.WriteLine($" Commit:            {commitSha}");
        Reporter.Output.WriteLine($" Workload version:  {WorkloadInfoHelper.GetWorkloadsVersion()}");
        Reporter.Output.WriteLine($" MSBuild version:   {MSBuildForwardingAppWithoutLogging.MSBuildVersion}");
        Reporter.Output.WriteLine();
        Reporter.Output.WriteLine($"{LocalizableStrings.DotNetRuntimeInfoLabel}");
        Reporter.Output.WriteLine($" OS Name:     {RuntimeEnvironment.OperatingSystem}");
        Reporter.Output.WriteLine($" OS Version:  {RuntimeEnvironment.OperatingSystemVersion}");
        Reporter.Output.WriteLine($" OS Platform: {RuntimeEnvironment.OperatingSystemPlatform}");
        Reporter.Output.WriteLine($" RID:         {GetDisplayRid(versionFile)}");
        Reporter.Output.WriteLine($" Base Path:   {AppContext.BaseDirectory}");
        Reporter.Output.WriteLine();
        Reporter.Output.WriteLine($"{LocalizableStrings.DotnetWorkloadInfoLabel}");
        new WorkloadInfoHelper(isInteractive: false).ShowWorkloadsInfo(showVersion: false);

        return 0;
    }

    private static string? GetDisplayRid(DotnetVersionFile versionFile)
    {
        FrameworkDependencyFile fxDepsFile = new();
        string currentRid = RuntimeInformation.RuntimeIdentifier;
        // If the current RID isn't supported by the shared framework, display the RID the CLI was built with instead,
        // so the user knows which RID they should put in their "runtimes" section.
        return fxDepsFile.IsRuntimeSupported(currentRid) ? currentRid : versionFile.BuildRid;
    }
}

internal class PrintCliSchemaAction(Option<bool> option) : InvocableOptionAction(option)
{
    public override bool Terminating => true;

    public override int Invoke(ParseResult parseResult)
    {
        if (!parseResult.HasOption(Option) || !parseResult.GetValue(option))
        {
            return 0;
        }

        CliSchema.PrintCliSchema(parseResult, parseResult.InvocationConfiguration.Output, Program.TelemetryInstance);

        return 0;
    }
}