|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Cli.Utils.Extensions;
using Microsoft.TemplateEngine.Abstractions;
using Microsoft.TemplateEngine.Cli.Commands;
using Microsoft.TemplateEngine.Cli.TabularOutput;
using Microsoft.TemplateEngine.Cli.TemplateResolution;
using Microsoft.TemplateEngine.Edge;
using Microsoft.TemplateEngine.Edge.Settings;
namespace Microsoft.TemplateEngine.Cli
{
internal class TemplateListCoordinator
{
private readonly IEngineEnvironmentSettings _engineEnvironmentSettings;
private readonly TemplatePackageManager _templatePackageManager;
private readonly IHostSpecificDataLoader _hostSpecificDataLoader;
private readonly string? _defaultLanguage;
private readonly TemplateConstraintManager _constraintManager;
internal TemplateListCoordinator(
IEngineEnvironmentSettings engineEnvironmentSettings,
TemplatePackageManager templatePackageManager,
IHostSpecificDataLoader hostSpecificDataLoader)
{
_engineEnvironmentSettings = engineEnvironmentSettings ?? throw new ArgumentNullException(nameof(engineEnvironmentSettings));
_templatePackageManager = templatePackageManager ?? throw new ArgumentNullException(nameof(templatePackageManager));
_hostSpecificDataLoader = hostSpecificDataLoader ?? throw new ArgumentNullException(nameof(hostSpecificDataLoader));
_defaultLanguage = engineEnvironmentSettings.GetDefaultLanguage();
using var constraintManagerActivity = Activities.Source.StartActivity("create-constraints");
_constraintManager = new TemplateConstraintManager(_engineEnvironmentSettings);
}
/// <summary>
/// Handles template list display (dotnet new --list).
/// </summary>
/// <param name="args">user command input.</param>
/// <param name="cancellationToken">cancellation token.</param>
/// <returns></returns>
internal async Task<NewCommandStatus> DisplayTemplateGroupListAsync(
ListCommandArgs args,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
ListTemplateResolver resolver = new(_constraintManager, _templatePackageManager, _hostSpecificDataLoader);
TemplateResolutionResult resolutionResult = await resolver.ResolveTemplatesAsync(args, _defaultLanguage, cancellationToken).ConfigureAwait(false);
if (resolutionResult.TemplateGroupsWithMatchingTemplateInfoAndParameters.Any())
{
Reporter.Output.WriteLine(LocalizableStrings.TemplatesFoundMatchingInputParameters, GetInputParametersString(args));
Reporter.Output.WriteLine();
TabularOutputSettings settings = new(_engineEnvironmentSettings.Environment, args);
TemplateGroupDisplay.DisplayTemplateList(
_engineEnvironmentSettings,
resolutionResult.TemplateGroupsWithMatchingTemplateInfoAndParameters,
settings,
reporter: Reporter.Output,
selectedLanguage: args.Language);
return NewCommandStatus.Success;
}
else
{
// If there is no criteria and filters, it means that dotnet new list was run but there are no templates installed.
if (args.ListNameCriteria == null && !args.AppliedFilters.Any())
{
// No templates installed.
Reporter.Output.WriteLine(LocalizableStrings.NoTemplatesFound);
Reporter.Output.WriteLine();
// To search for the templates on NuGet.org, run:
Reporter.Output.WriteLine(LocalizableStrings.Generic_CommandHints_Search);
Reporter.Output.WriteCommand(
Example
.For<NewCommand>(args.ParseResult)
.WithSubcommand<SearchCommand>()
.WithArgument(BaseSearchCommand.NameArgument));
Reporter.Output.WriteLine();
return NewCommandStatus.Success;
}
// At least one criteria was specified.
// No templates found matching the following input parameter(s): {0}.
Reporter.Error.WriteLine(
string.Format(
LocalizableStrings.Generic_Info_NoMatchingTemplates,
GetInputParametersString(args/*, appliedParameterMatches*/))
.Bold().Red());
if (resolutionResult.HasTemplateGroupMatches && resolutionResult.ListFilterMismatchGroupCount > 0)
{
// {0} template(s) partially matched, but failed on {1}.
Reporter.Error.WriteLine(
string.Format(
LocalizableStrings.TemplatesNotValidGivenTheSpecifiedFilter,
resolutionResult.ListFilterMismatchGroupCount,
GetPartialMatchReason(resolutionResult, args/*, appliedParameterMatches*/))
.Bold().Red());
}
if (resolutionResult.HasTemplateGroupMatches && resolutionResult.ContraintsMismatchGroupCount > 0)
{
// {0} template(s) are not displayed due to their constraints are not satisfied.
// To display them add "--ignore-constraints" option.
Reporter.Error.WriteLine(
string.Format(
LocalizableStrings.TemplateListCoordinator_Error_FailedConstraints,
resolutionResult.ContraintsMismatchGroupCount,
BaseListCommand.IgnoreConstraintsOption.Name)
.Bold().Red());
}
Reporter.Error.WriteLine();
// To search for the templates on NuGet.org, run:
Reporter.Error.WriteLine(LocalizableStrings.Generic_CommandHints_Search);
if (string.IsNullOrWhiteSpace(args.ListNameCriteria))
{
Reporter.Error.WriteCommand(
Example
.For<NewCommand>(args.ParseResult)
.WithSubcommand<SearchCommand>()
.WithArgument(BaseSearchCommand.NameArgument));
}
else
{
Reporter.Error.WriteCommand(
Example
.For<NewCommand>(args.ParseResult)
.WithSubcommand<SearchCommand>()
.WithArgument(BaseSearchCommand.NameArgument, args.ListNameCriteria));
}
Reporter.Error.WriteLine();
return NewCommandStatus.NotFound;
}
}
/// <summary>
/// Handles display for dotnet new command without parameters.
/// </summary>
/// <param name="args">command arguments.</param>
/// <param name="cancellationToken">cancellation token.</param>
/// <returns></returns>
internal async Task<NewCommandStatus> DisplayCommandDescriptionAsync(
GlobalArgs args,
CancellationToken cancellationToken)
{
IEnumerable<ITemplateInfo> curatedTemplates = await GetCuratedListAsync(cancellationToken).ConfigureAwait(false);
Reporter.Output.WriteLine(LocalizableStrings.TemplateInformationCoordinator_DotnetNew_Description, Example.For<NewCommand>(args.ParseResult));
Reporter.Output.WriteLine();
Reporter.Output.WriteLine(LocalizableStrings.TemplateInformationCoordinator_DotnetNew_TemplatesHeader, Example.For<NewCommand>(args.ParseResult));
TemplateGroupDisplay.DisplayTemplateList(
_engineEnvironmentSettings,
curatedTemplates,
new TabularOutputSettings(_engineEnvironmentSettings.Environment),
reporter: Reporter.Output);
Reporter.Output.WriteLine(LocalizableStrings.TemplateInformationCoordinator_DotnetNew_ExampleHeader);
Reporter.Output.WriteCommand(
Example
.For<NewCommand>(args.ParseResult)
.WithArgument(NewCommand.ShortNameArgument, "console"));
Reporter.Output.WriteLine();
Reporter.Output.WriteLine(LocalizableStrings.TemplateInformationCoordinator_DotnetNew_DisplayOptionsHint);
Reporter.Output.WriteCommand(
Example
.For<NewCommand>(args.ParseResult)
.WithArgument(NewCommand.ShortNameArgument, "console")
.WithHelpOption());
Reporter.Output.WriteLine(LocalizableStrings.TemplateInformationCoordinator_DotnetNew_ListTemplatesHint);
Reporter.Output.WriteCommand(
Example
.For<NewCommand>(args.ParseResult)
.WithSubcommand<ListCommand>());
Reporter.Output.WriteLine(LocalizableStrings.TemplateInformationCoordinator_DotnetNew_SearchTemplatesHint);
Reporter.Output.WriteCommand(
Example
.For<NewCommand>(args.ParseResult)
.WithSubcommand<SearchCommand>()
.WithArgument(BaseSearchCommand.NameArgument, "web"));
Reporter.Output.WriteLine();
return NewCommandStatus.Success;
}
private static string GetInputParametersString(ListCommandArgs args)
{
string separator = ", ";
IEnumerable<string> appliedFilters = args.AppliedFilters
.Select(filter => $"{args.GetFilterToken(filter)}='{args.GetFilterValue(filter)}'");
StringBuilder inputParameters = new();
string? mainCriteria = args.ListNameCriteria;
if (!string.IsNullOrWhiteSpace(mainCriteria))
{
inputParameters.Append($"'{mainCriteria}'");
if (appliedFilters.Any())
{
inputParameters.Append(separator);
}
}
if (appliedFilters.Any())
{
inputParameters.Append(string.Join(separator, appliedFilters));
}
return inputParameters.ToString();
}
private static string GetPartialMatchReason(TemplateResolutionResult templateResolutionResult, ListCommandArgs args)
{
string separator = ", ";
IEnumerable<string> appliedFilters = args.AppliedFilters
.OfType<TemplateFilterOptionDefinition>()
.Where(filter => filter.MismatchCriteria(templateResolutionResult))
.Select(filter => $"{args.GetFilterToken(filter)}='{args.GetFilterValue(filter)}'");
StringBuilder inputParameters = new();
if (appliedFilters.Any())
{
inputParameters.Append(string.Join(separator, appliedFilters));
}
return inputParameters.ToString();
}
/// <summary>
/// Displays curated list of templates for dotnet new command.
/// </summary>
private async Task<IEnumerable<ITemplateInfo>> GetCuratedListAsync(CancellationToken cancellationToken)
{
string[] curatedGroupIdentityList = new[]
{
"Microsoft.Common.Library", //classlib
"Microsoft.Common.Console", //console
"Microsoft.Common.WPF", //wpf
"Microsoft.Common.WinForms", //winforms
"Microsoft.Web.Blazor", //blazor
"Microsoft.Test.MSTest" //mstest
};
IReadOnlyList<ITemplateInfo> templates = await _templatePackageManager.GetTemplatesAsync(cancellationToken).ConfigureAwait(false);
return templates.Where(t => curatedGroupIdentityList.Contains(t.GroupIdentity, StringComparer.OrdinalIgnoreCase));
}
}
}
|