Skip to content

Commit

Permalink
fixes #3805 install subcommand
Browse files Browse the repository at this point in the history
* fixed existing tests; added TestHost.GetVirtualHost helper method

Added possibility to exclude test templates

* install command parser tests; fixed install integration tests

updated System.CommandLine to latest version
tabular output refactoring (still needs parsing of --columns and --columns-all

* fixed tab completion tests on unix/macos

* help builder fix

* implemented validation for incorrect order of options in non-legacy scenario
  • Loading branch information
vlada-shubina committed Jan 28, 2022
1 parent dd85912 commit a7409d8
Show file tree
Hide file tree
Showing 26 changed files with 718 additions and 404 deletions.
14 changes: 5 additions & 9 deletions src/Microsoft.TemplateEngine.Cli/Alias/AliasSupport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
using System.Text.RegularExpressions;
using Microsoft.TemplateEngine.Abstractions;
using Microsoft.TemplateEngine.Cli.CommandParsing;
using Microsoft.TemplateEngine.Cli.TableOutput;
using Microsoft.TemplateEngine.Cli.TabularOutput;

namespace Microsoft.TemplateEngine.Cli.Alias
{
Expand Down Expand Up @@ -117,14 +117,10 @@ internal static NewCommandStatus DisplayAliasValues(IEngineEnvironmentSettings e
Reporter.Output.WriteLine(LocalizableStrings.AliasShowAllAliasesHeader);
}

HelpFormatter<KeyValuePair<string, IReadOnlyList<string>>> formatter =
new HelpFormatter<KeyValuePair<string, IReadOnlyList<string>>>(
environment,
commandInput,
aliasesToShow,
columnPadding: 2,
headerSeparator: '-',
blankLineBetweenRows: false)
TabularOutput<KeyValuePair<string, IReadOnlyList<string>>> formatter =
new TabularOutput<KeyValuePair<string, IReadOnlyList<string>>>(
new CliTabularOutputSettings(environment.Environment),
aliasesToShow)
.DefineColumn(t => t.Key, LocalizableStrings.AliasName, showAlways: true)
.DefineColumn(t => string.Join(" ", t.Value), LocalizableStrings.AliasValue, showAlways: true);

Expand Down
10 changes: 10 additions & 0 deletions src/Microsoft.TemplateEngine.Cli/Commands/BaseCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,16 @@ public override IEnumerable<string> GetSuggestions(ParseResult? parseResult = nu
return GetSuggestions(args, environmentSettings, textToMatch);
}

protected static string? ValidateOptionUsageInParent(CommandResult symbolResult, Option option)
{
OptionResult? optionResult = symbolResult.Parent?.Children.FirstOrDefault(symbol => symbol.Symbol == option) as OptionResult;
if (optionResult != null)
{
return $"Option '{optionResult.Token?.Value}' should be used after '{symbolResult.Symbol.Name}'.";
}
return null;
}

protected virtual IEnumerable<string> GetSuggestions(TArgs args, IEngineEnvironmentSettings environmentSettings, string? textToMatch)
{
return base.GetSuggestions(args.ParseResult, textToMatch);
Expand Down
44 changes: 23 additions & 21 deletions src/Microsoft.TemplateEngine.Cli/Commands/InstallCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,9 @@ public InstallCommand(
NewCommandCallbacks callbacks)
: base(host, logger, callbacks, "install")
{
AddValidator(ValidateLegacyUsage);
_legacyInstallCommand = legacyInstallCommand;
}

private string? ValidateLegacyUsage(CommandResult symbolResult)
{
//if (symbolResult.Parent!.Children.Any((a)=> a // _legacyInstallCommand.InteractiveOption))
//{
// return "We are doomed!";
//}
return null;
AddValidator(symbolResult => ValidateOptionUsageInParent(symbolResult, _legacyInstallCommand.InteractiveOption));
AddValidator(symbolResult => ValidateOptionUsageInParent(symbolResult, _legacyInstallCommand.AddSourceOption));
}
}

Expand All @@ -47,18 +39,15 @@ public LegacyInstallCommand(NewCommand newCommand, ITemplateEngineHost host, ITe
this.IsHidden = true;
this.AddAlias("-i");
AddSourceOption.AddAlias("--nuget-source");

//the user should use --nuget-source A --nuget-source B to specify multiple options
AddSourceOption.AllowMultipleArgumentsPerToken = false;
AddSourceOption.IsHidden = true;
InteractiveOption.IsHidden = true;

newCommand.AddOption(AddSourceOption);
newCommand.AddOption(InteractiveOption);
}

public void AddOptionsToNewCommand(Command rootCommand)
{
rootCommand.Add(AddSourceOption);
rootCommand.Add(InteractiveOption);
}
}

internal abstract class BaseInstallCommand : BaseCommand<InstallCommandArgs>
Expand All @@ -82,14 +71,13 @@ internal BaseInstallCommand(ITemplateEngineHost host, ITelemetryLogger logger, N
Description = "When downloading enable NuGet interactive."
};

internal Option<IReadOnlyList<string>> AddSourceOption { get; } = new(new[] { "--add-source" })
internal Option<IReadOnlyList<string>> AddSourceOption { get; } = new(new[] { "--add-source", "--nuget-source" })
{
Description = "Add NuGet source when looking for package.",
AllowMultipleArgumentsPerToken = true,
IsHidden = true
};

protected override Task<NewCommandStatus> ExecuteAsync(InstallCommandArgs args, IEngineEnvironmentSettings environmentSettings, InvocationContext context)
protected override async Task<NewCommandStatus> ExecuteAsync(InstallCommandArgs args, IEngineEnvironmentSettings environmentSettings, InvocationContext context)
{
using TemplatePackageManager templatePackageManager = new TemplatePackageManager(environmentSettings);
TemplateInformationCoordinator templateInformationCoordinator = new TemplateInformationCoordinator(
Expand All @@ -107,7 +95,8 @@ protected override Task<NewCommandStatus> ExecuteAsync(InstallCommandArgs args,
templateInformationCoordinator,
environmentSettings.GetDefaultLanguage());

return templatePackageCoordinator.EnterInstallFlowAsync(args, context.GetCancellationToken());
//TODO: we need to await, otherwise templatePackageManager will be disposed.
return await templatePackageCoordinator.EnterInstallFlowAsync(args, context.GetCancellationToken()).ConfigureAwait(false);
}

protected override InstallCommandArgs ParseContext(ParseResult parseResult)
Expand All @@ -120,7 +109,20 @@ internal class InstallCommandArgs : GlobalArgs
{
public InstallCommandArgs(BaseInstallCommand installCommand, ParseResult parseResult) : base(installCommand, parseResult)
{
TemplatePackages = parseResult.GetValueForArgument(installCommand.NameArgument) ?? throw new Exception("This shouldn't happen, we set ArgumentArity(1)...");
TemplatePackages = parseResult.GetValueForArgument(installCommand.NameArgument)
?? throw new ArgumentException($"{nameof(parseResult)} should contain at least one argument for {nameof(installCommand.NameArgument)}", nameof(parseResult));

//workaround for --install source1 --install source2 case
if (installCommand is LegacyInstallCommand && installCommand.Aliases.Any(alias => TemplatePackages.Contains(alias)))
{
TemplatePackages = TemplatePackages.Where(package => !installCommand.Aliases.Contains(package)).ToList();
}

if (!TemplatePackages.Any())
{
throw new ArgumentException($"{nameof(parseResult)} should contain at least one argument for {nameof(installCommand.NameArgument)}", nameof(parseResult));
}

Interactive = parseResult.GetValueForOption(installCommand.InteractiveOption);
AdditionalSources = parseResult.GetValueForOption(installCommand.AddSourceOption);
}
Expand Down
20 changes: 14 additions & 6 deletions src/Microsoft.TemplateEngine.Cli/Commands/NewCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
#nullable enable

using System.CommandLine;
using System.CommandLine.Help;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using System.CommandLine.Parsing;
Expand Down Expand Up @@ -55,20 +54,25 @@ protected override IEnumerable<string> GetSuggestions(NewCommandArgs args, IEngi
using TemplatePackageManager templatePackageManager = new TemplatePackageManager(environmentSettings);
var templates = templatePackageManager.GetTemplatesAsync(CancellationToken.None).Result;

//TODO: implement correct logic
if (!string.IsNullOrEmpty(args.ShortName))
{
var template = templates.FirstOrDefault(template => template.ShortNameList.Contains(args.ShortName));
var matchingTemplates = templates.Where(template => template.ShortNameList.Contains(args.ShortName));
HashSet<string> distinctSuggestions = new HashSet<string>();

if (template != null)
foreach (var template in matchingTemplates)
{
var templateGroupCommand = new TemplateGroupCommand(this, environmentSettings, template);
var parsed = templateGroupCommand.Parse(args.Arguments ?? Array.Empty<string>());
foreach (var suggestion in templateGroupCommand.GetSuggestions(parsed, textToMatch))
{
yield return suggestion;
if (distinctSuggestions.Add(suggestion))
{
yield return suggestion;
}
}
yield break;
}
yield break;
}
else
{
Expand All @@ -93,7 +97,11 @@ protected override async Task<NewCommandStatus> ExecuteAsync(NewCommandArgs args
{
if (args.HelpRequested)
{
context.HelpBuilder.Write(context.ParseResult.CommandResult.Command, StandardStreamWriter.Create(context.Console.Out));
context.HelpBuilder.Write(
context.ParseResult.CommandResult.Command,
StandardStreamWriter.Create(context.Console.Out),
context.ParseResult);

return NewCommandStatus.Success;
}
//show curated list
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.CommandLine;
using System.CommandLine.Help;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using Microsoft.TemplateEngine.Abstractions;
Expand Down Expand Up @@ -34,7 +33,10 @@ public async Task<int> InvokeAsync(InvocationContext context)

if (templateGroupArgs.HelpRequested)
{
context.HelpBuilder.Write(context.ParseResult.CommandResult.Command, StandardStreamWriter.Create(context.Console.Out));
context.HelpBuilder.Write(
context.ParseResult.CommandResult.Command,
StandardStreamWriter.Create(context.Console.Out),
context.ParseResult);
return 0;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
using Microsoft.TemplateEngine.Abstractions;
using Microsoft.TemplateEngine.Abstractions.TemplateFiltering;
using Microsoft.TemplateEngine.Cli.CommandParsing;
using Microsoft.TemplateEngine.Cli.TableOutput;
using Microsoft.TemplateEngine.Cli.TabularOutput;
using Microsoft.TemplateEngine.Cli.TemplateResolution;
using Microsoft.TemplateEngine.Utils;
using TemplateCreator = Microsoft.TemplateEngine.Edge.Template.TemplateCreator;
Expand Down Expand Up @@ -103,7 +103,9 @@ private static void ShowParameterHelp(IReadOnlyDictionary<string, string?> input

if (filteredParams.Any())
{
HelpFormatter<ITemplateParameter> formatter = new HelpFormatter<ITemplateParameter>(environmentSettings, commandInput, filteredParams, 2, null, true);
TabularOutput<ITemplateParameter> formatter = new TabularOutput<ITemplateParameter>(
new CliTabularOutputSettings(environmentSettings.Environment, headerSeparator: null, blankLineBetweenRows: true),
filteredParams);

formatter.DefineColumn(
param =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
using Microsoft.TemplateEngine.Abstractions;
using Microsoft.TemplateEngine.Abstractions.TemplatePackage;
using Microsoft.TemplateEngine.Cli.CommandParsing;
using Microsoft.TemplateEngine.Cli.TableOutput;
using Microsoft.TemplateEngine.Cli.TabularOutput;
using Microsoft.TemplateEngine.Cli.TemplateResolution;
using Microsoft.TemplateEngine.Edge.Settings;
using Microsoft.TemplateEngine.Utils;
Expand All @@ -24,6 +24,8 @@ internal class TemplateInformationCoordinator
private readonly ITelemetryLogger _telemetryLogger;
private readonly string? _defaultLanguage;

private readonly ITabularOutputSettings _defaultTabularOutputSettings;

internal TemplateInformationCoordinator(
IEngineEnvironmentSettings engineEnvironmentSettings,
TemplatePackageManager templatePackageManager,
Expand All @@ -39,6 +41,8 @@ internal TemplateInformationCoordinator(
_hostSpecificDataLoader = hostSpecificDataLoader ?? throw new ArgumentNullException(nameof(hostSpecificDataLoader));
_telemetryLogger = telemetryLogger ?? throw new ArgumentNullException(nameof(telemetryLogger));
_defaultLanguage = defaultLanguage;

_defaultTabularOutputSettings = new CliTabularOutputSettings(engineEnvironmentSettings.Environment);
}

/// <summary>
Expand Down Expand Up @@ -71,12 +75,12 @@ internal Task<NewCommandStatus> CoordinateAmbiguousTemplateResolutionDisplayAsyn
return Task.FromResult(NewCommandStatus.NotFound);
case TemplateResolutionResult.Status.AmbiguousLanguageChoice:
Reporter.Error.WriteLine(LocalizableStrings.AmbiguousTemplateGroupListHeader.Bold().Red());
DisplayTemplateList(resolutionResult.TemplateGroups, commandInput, useErrorOutput: true);
DisplayTemplateList(resolutionResult.TemplateGroups, _defaultTabularOutputSettings, useErrorOutput: true);
Reporter.Error.WriteLine(LocalizableStrings.AmbiguousLanguageHint.Bold().Red());
return Task.FromResult(NewCommandStatus.NotFound);
case TemplateResolutionResult.Status.AmbiguousTemplateGroupChoice:
Reporter.Error.WriteLine(LocalizableStrings.AmbiguousTemplateGroupListHeader.Bold().Red());
DisplayTemplateList(resolutionResult.TemplateGroups, commandInput, useErrorOutput: true);
DisplayTemplateList(resolutionResult.TemplateGroups, _defaultTabularOutputSettings, useErrorOutput: true);
//TODO: https://github.com/dotnet/templating/issues/3275
//revise error handling: this message is not the best CTA
//Reporter.Error.WriteLine(LocalizableStrings.AmbiguousTemplateGroupListHint.Bold().Red());
Expand Down Expand Up @@ -122,15 +126,16 @@ internal Task<NewCommandStatus> CoordinateAmbiguousTemplateResolutionDisplayAsyn
/// </summary>
internal void DisplayTemplateList(
IEnumerable<TemplateGroup> templateGroups,
INewCommandInput commandInput,
ITabularOutputSettings helpFormatterSettings,
string? selectedLanguage = null,
bool useErrorOutput = false)
{
IReadOnlyCollection<TemplateGroupTableRow> groupsForDisplay = TemplateGroupDisplay.GetTemplateGroupsForListDisplay(
templateGroups,
commandInput.Language,
selectedLanguage,
_defaultLanguage,
_engineEnvironmentSettings.Environment);
DisplayTemplateList(groupsForDisplay, commandInput, useErrorOutput);
DisplayTemplateList(groupsForDisplay, helpFormatterSettings, useErrorOutput);
}

/// <summary>
Expand All @@ -147,15 +152,16 @@ internal void DisplayTemplateList(
/// </summary>
internal void DisplayTemplateList(
IEnumerable<ITemplateInfo> templates,
INewCommandInput commandInput,
ITabularOutputSettings helpFormatterSettings,
string? selectedLanguage = null,
bool useErrorOutput = false)
{
IReadOnlyCollection<TemplateGroupTableRow> groupsForDisplay = TemplateGroupDisplay.GetTemplateGroupsForListDisplay(
templates,
commandInput.Language,
selectedLanguage,
_defaultLanguage,
_engineEnvironmentSettings.Environment);
DisplayTemplateList(groupsForDisplay, commandInput, useErrorOutput);
DisplayTemplateList(groupsForDisplay, helpFormatterSettings, useErrorOutput);
}

internal void ShowUsageHelp(INewCommandInput commandInput)
Expand Down Expand Up @@ -233,7 +239,7 @@ internal async Task<NewCommandStatus> DisplayTemplateGroupListAsync(
LocalizableStrings.TemplatesFoundMatchingInputParameters,
GetInputParametersString(resolutionResult.Resolver.Filters, commandInput, appliedParameterMatches)));
Reporter.Output.WriteLine();
DisplayTemplateList(resolutionResult.TemplateGroupsWithMatchingTemplateInfoAndParameters, commandInput);
DisplayTemplateList(resolutionResult.TemplateGroupsWithMatchingTemplateInfoAndParameters, _defaultTabularOutputSettings, selectedLanguage: commandInput.Language);
return NewCommandStatus.Success;
}
else
Expand Down Expand Up @@ -292,7 +298,7 @@ internal async Task<NewCommandStatus> DisplayCommandDescriptionAsync(
Reporter.Output.WriteLine(string.Format(
LocalizableStrings.TemplateInformationCoordinator_DotnetNew_TemplatesHeader,
commandInput.New3CommandExample()));
DisplayTemplateList(curatedTemplates, commandInput);
DisplayTemplateList(curatedTemplates, _defaultTabularOutputSettings);

Reporter.Output.WriteLine(LocalizableStrings.TemplateInformationCoordinator_DotnetNew_ExampleHeader);
Reporter.Output.WriteCommand(commandInput.InstantiateTemplateExample("console"));
Expand Down Expand Up @@ -430,15 +436,11 @@ private async Task<NewCommandStatus> DisplayAmbiguousPrecedenceErrorAsync(
});
}

HelpFormatter<AmbiguousTemplateDetails> formatter =
HelpFormatter
TabularOutput<AmbiguousTemplateDetails> formatter =
TabularOutput.TabularOutput
.For(
_engineEnvironmentSettings,
commandInput,
ambiguousTemplateDetails,
columnPadding: 2,
headerSeparator: '-',
blankLineBetweenRows: false)
_defaultTabularOutputSettings,
ambiguousTemplateDetails)
.DefineColumn(t => t.TemplateIdentity, out object identityColumn, LocalizableStrings.ColumnNameIdentity, showAlways: true)
.DefineColumn(t => t.TemplateName, LocalizableStrings.ColumnNameTemplateName, shrinkIfNeeded: true, minWidth: 15, showAlways: true)
.DefineColumn(t => string.Join(",", t.TemplateShortNames), LocalizableStrings.ColumnNameShortName, showAlways: true)
Expand Down Expand Up @@ -466,18 +468,14 @@ private async Task<NewCommandStatus> DisplayAmbiguousPrecedenceErrorAsync(

private void DisplayTemplateList(
IReadOnlyCollection<TemplateGroupTableRow> groupsForDisplay,
INewCommandInput commandInput,
ITabularOutputSettings tabularOutputSettings,
bool useErrorOutput = false)
{
HelpFormatter<TemplateGroupTableRow> formatter =
HelpFormatter
TabularOutput<TemplateGroupTableRow> formatter =
TabularOutput.TabularOutput
.For(
_engineEnvironmentSettings,
commandInput,
groupsForDisplay,
columnPadding: 2,
headerSeparator: '-',
blankLineBetweenRows: false)
tabularOutputSettings,
groupsForDisplay)
.DefineColumn(t => t.Name, out object nameColumn, LocalizableStrings.ColumnNameTemplateName, shrinkIfNeeded: true, minWidth: 15, showAlways: true)
.DefineColumn(t => t.ShortNames, LocalizableStrings.ColumnNameShortName, showAlways: true)
.DefineColumn(t => t.Languages, out object languageColumn, LocalizableStrings.ColumnNameLanguage, BaseCommandInput.LanguageColumnFilter, defaultColumn: true)
Expand Down
Loading

0 comments on commit a7409d8

Please sign in to comment.