Skip to content

Commit

Permalink
fixes dotnet#3804 implements search subcommand
Browse files Browse the repository at this point in the history
  • Loading branch information
vlada-shubina committed Oct 25, 2021
1 parent 1449a0a commit 5aa2568
Show file tree
Hide file tree
Showing 32 changed files with 1,255 additions and 349 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ private static Option[] NewCommandVisibleArgs
Create.Option("-u|--uninstall", LocalizableStrings.UninstallHelp, Accept.ZeroOrMoreArguments()),
Create.Option("--interactive", LocalizableStrings.OptionDescriptionInteractive, Accept.NoArguments()),
Create.Option("--nuget-source|--add-source", LocalizableStrings.OptionDescriptionNuGetSource, Accept.OneOrMoreArguments()),
Create.Option("--type", LocalizableStrings.ShowsFilteredTemplates, Accept.ExactlyOneArgument()),
Create.Option("--type", LocalizableStrings.OptionDescriptionTypeFilter, Accept.ExactlyOneArgument()),
Create.Option("--dry-run", LocalizableStrings.DryRunDescription, Accept.NoArguments()),
Create.Option("--force", LocalizableStrings.ForcesTemplateCreation, Accept.NoArguments()),
Create.Option("-lang|--language", LocalizableStrings.LanguageParameter, Accept.ExactlyOneArgument()),
Create.Option("-lang|--language", LocalizableStrings.OptionDescriptionLanguageFilter, Accept.ExactlyOneArgument()),
Create.Option("--update-check", LocalizableStrings.UpdateCheckCommandHelp, Accept.NoArguments()),
Create.Option("--update-apply", LocalizableStrings.UpdateApplyCommandHelp, Accept.NoArguments()),
Create.Option(
Expand Down
34 changes: 34 additions & 0 deletions src/Microsoft.TemplateEngine.Cli/Commands/BaseCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,17 @@ public override IEnumerable<string> GetSuggestions(ParseResult? parseResult = nu
return null;
}

protected static string? ValidateArgumentUsageInParent(SymbolResult symbolResult, Argument argument)
{
CommandResult commandResult = symbolResult as CommandResult ?? throw new Exception("Validator should be used with command");
var newCommandArgument = commandResult.Parent?.Children.FirstOrDefault(symbol => symbol.Symbol == argument) as ArgumentResult;
if (newCommandArgument != null)
{
return $"Invalid command syntax: argument '{newCommandArgument.Tokens[0].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 All @@ -172,6 +183,29 @@ protected IEngineEnvironmentSettings CreateEnvironmentSettings(TArgs args)

protected abstract TArgs ParseContext(ParseResult parseResult);

protected virtual Option GetFilterOption(FilterOptionDefinition def)
{
return def.OptionFactory();
}

protected IReadOnlyDictionary<FilterOptionDefinition, Option> SetupFilterOptions(IReadOnlyList<FilterOptionDefinition> filtersToSetup)
{
Dictionary<FilterOptionDefinition, Option> options = new Dictionary<FilterOptionDefinition, Option>();
foreach (var filterDef in filtersToSetup)
{
var newOption = GetFilterOption(filterDef);
this.AddOption(newOption);
options[filterDef] = newOption;
}
return options;
}

protected void SetupTabularOutputOptions(ITabularOutputCommand command)
{
this.AddOption(command.ColumnsAllOption);
this.AddOption(command.ColumnsOption);
}

private static async Task<AsyncMutex?> EnsureEntryMutex(TArgs args, IEngineEnvironmentSettings environmentSettings, CancellationToken token)
{
// we don't need to acquire mutex in case of virtual settings
Expand Down
142 changes: 142 additions & 0 deletions src/Microsoft.TemplateEngine.Cli/Commands/FilterOptionDefinition.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable enable

using System.CommandLine;
using Microsoft.TemplateEngine.Abstractions;
using Microsoft.TemplateEngine.Abstractions.TemplateFiltering;
using Microsoft.TemplateEngine.Cli.TemplateResolution;
using Microsoft.TemplateEngine.Utils;
using Microsoft.TemplateSearch.Common.Abstractions;

namespace Microsoft.TemplateEngine.Cli.Commands
{
/// <summary>
/// Defines supported dotnet new command filter option
/// Filter options can be used along with other dotnet new subcommands to filter the required items for the action, for example for list subcommand filters can limit the templates to be shown.
/// </summary>
internal class FilterOptionDefinition
{
internal FilterOptionDefinition(string name, Func<Option> optionFactory)
{
Name = string.IsNullOrWhiteSpace(name) ? throw new ArgumentException($"{nameof(name)} should not be null or whitespace", nameof(name)) : name;
OptionFactory = optionFactory ?? throw new ArgumentNullException(nameof(optionFactory));
}

internal static FilterOptionDefinition AuthorFilter { get; } =
new TemplateFilterOptionDefinition(
"author",
optionFactory: () => SharedOptionsFactory.GetAuthorOption(),
matchFilter: authorArg => WellKnownSearchFilters.AuthorFilter(authorArg),
mismatchCriteria: resolutionResult => resolutionResult.HasAuthorMismatch);

internal static FilterOptionDefinition BaselineFilter { get; } =
new TemplateFilterOptionDefinition(
"baseline",
optionFactory: () => SharedOptionsFactory.GetBaselineOption(),
matchFilter: baselineArg => WellKnownSearchFilters.BaselineFilter(baselineArg),
mismatchCriteria: resolutionResult => resolutionResult.HasBaselineMismatch);

internal static FilterOptionDefinition LanguageFilter { get; } =
new TemplateFilterOptionDefinition(
"language",
optionFactory: () => SharedOptionsFactory.GetLanguageOption(),
matchFilter: languageArg => WellKnownSearchFilters.LanguageFilter(languageArg),
mismatchCriteria: resolutionResult => resolutionResult.HasLanguageMismatch);

internal static FilterOptionDefinition TagFilter { get; } =
new TemplateFilterOptionDefinition(
"tag",
optionFactory: () => SharedOptionsFactory.GetTagOption(),
matchFilter: tagArg => WellKnownSearchFilters.ClassificationFilter(tagArg),
mismatchCriteria: resolutionResult => resolutionResult.HasClassificationMismatch);

internal static FilterOptionDefinition TypeFilter { get; } =
new TemplateFilterOptionDefinition(
"type",
optionFactory: () => SharedOptionsFactory.GetTypeOption(),
matchFilter: typeArg => WellKnownSearchFilters.TypeFilter(typeArg),
mismatchCriteria: resolutionResult => resolutionResult.HasTypeMismatch);

internal static FilterOptionDefinition PackageFilter { get; } =
new PackageFilterOptionDefinition(
"package",
optionFactory: () => SharedOptionsFactory.GetPackageOption(),
matchFilter: PackageMatchFilter);

//TODO: check if it's needed

/// <summary>
/// Name of the option, should match option long name.
/// </summary>
internal string Name { get; }

/// <summary>
/// A predicate that creates instance of option.
/// </summary>
internal Func<Option> OptionFactory { get; }

private static Func<ITemplatePackageInfo, bool> PackageMatchFilter(string packageArg)
{
return (pack) =>
{
if (string.IsNullOrWhiteSpace(packageArg))
{
return true;
}
return pack.Name.IndexOf(packageArg, StringComparison.OrdinalIgnoreCase) > -1;
};
}
}

/// <summary>
/// Defines supported dotnet new command filter option applicable to the template.
/// </summary>
internal class TemplateFilterOptionDefinition : FilterOptionDefinition
{
internal TemplateFilterOptionDefinition(
string name,
Func<Option> optionFactory,
Func<string, Func<ITemplateInfo, MatchInfo?>> matchFilter,
Func<TemplateResolutionResult, bool> mismatchCriteria) : base(name, optionFactory)
{
TemplateMatchFilter = matchFilter ?? throw new ArgumentNullException(nameof(matchFilter));
MismatchCriteria = mismatchCriteria ?? throw new ArgumentNullException(nameof(mismatchCriteria));
}

/// <summary>
/// A predicate that returns the template match filter for the filter option.
/// Template match filter should return the MatchInfo for the given template based on filter value.
/// </summary>
/// <remarks>
/// Common template match filters are defined in Microsoft.TemplateEngine.Utils.WellKnonwnSearchFilter class.
/// </remarks>
internal Func<string, Func<ITemplateInfo, MatchInfo?>> TemplateMatchFilter { get; set; }

/// <summary>
/// A predicate that returns if the filter option caused a mismatch in <see cref="TemplateResolutionResult"/> in case of partial match.
/// </summary>
internal Func<TemplateResolutionResult, bool> MismatchCriteria { get; set; }
}

/// <summary>
/// Defines supported dotnet new command filter option applicable to the package.
/// </summary>
internal class PackageFilterOptionDefinition : FilterOptionDefinition
{
internal PackageFilterOptionDefinition(
string name,
Func<Option> optionFactory,
Func<string, Func<ITemplatePackageInfo, bool>> matchFilter) : base(name, optionFactory)
{
PackageMatchFilter = matchFilter ?? throw new ArgumentNullException(nameof(matchFilter));
}

/// <summary>
/// A predicate that returns the package match filter for the filter option
/// Package match filter should if package is a match based on filter value.
/// </summary>
internal Func<string, Func<ITemplatePackageInfo, bool>> PackageMatchFilter { get; set; }
}
}
20 changes: 19 additions & 1 deletion src/Microsoft.TemplateEngine.Cli/Commands/GlobalArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ public GlobalArgs(BaseCommand command, ParseResult parseResult)
DebugReinit = parseResult.GetValueForOption(command.DebugReinitOption);
DebugRebuildCache = parseResult.GetValueForOption(command.DebugRebuildCacheOption);
DebugShowConfig = parseResult.GetValueForOption(command.DebugShowConfigOption);
//TODO: check if it gets the command name correctly.
CommandName = GetNewCommandName(parseResult);
ParseResult = parseResult;
}
Expand All @@ -38,6 +37,25 @@ public GlobalArgs(BaseCommand command, ParseResult parseResult)

internal string? DebugCustomSettingsLocation { get; private set; }

protected static IReadOnlyDictionary<FilterOptionDefinition, string> ParseFilters(IFilterableCommand filterableCommand, ParseResult parseResult)
{
Dictionary<FilterOptionDefinition, string> filterValues = new Dictionary<FilterOptionDefinition, string>();
foreach (var filter in filterableCommand.Filters)
{
string? value = parseResult.GetValueForOption(filter.Value)?.ToString();
if (!string.IsNullOrWhiteSpace(value))
{
filterValues[filter.Key] = value;
}
}
return filterValues;
}

protected static (bool, IReadOnlyList<string>?) ParseTabularOutputSettings(ITabularOutputCommand command, ParseResult parseResult)
{
return (parseResult.GetValueForOption(command.ColumnsAllOption), parseResult.GetValueForOption(command.ColumnsOption));
}

private string GetNewCommandName(ParseResult parseResult)
{
var command = parseResult.CommandResult.Command;
Expand Down
19 changes: 19 additions & 0 deletions src/Microsoft.TemplateEngine.Cli/Commands/IFilterableCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable enable

using System.CommandLine;

namespace Microsoft.TemplateEngine.Cli.Commands
{
internal interface IFilterableCommand
{
IReadOnlyDictionary<FilterOptionDefinition, Option> Filters { get; }
}

internal interface IFilterableArgs
{
IReadOnlyDictionary<FilterOptionDefinition, string> Filters { get; }
}
}
23 changes: 23 additions & 0 deletions src/Microsoft.TemplateEngine.Cli/Commands/ITabularOutputCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable enable

using System.CommandLine;

namespace Microsoft.TemplateEngine.Cli.Commands
{
internal interface ITabularOutputCommand
{
internal Option<bool> ColumnsAllOption { get; }

internal Option<IReadOnlyList<string>> ColumnsOption { get; }
}

internal interface ITabularOutputArgs
{
internal bool DisplayAllColumns { get; }

internal IReadOnlyList<string>? ColumnsToDisplay { get; }
}
}
46 changes: 38 additions & 8 deletions src/Microsoft.TemplateEngine.Cli/Commands/NewCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ namespace Microsoft.TemplateEngine.Cli.Commands
{
internal class NewCommand : BaseCommand<NewCommandArgs>
{
private static readonly IReadOnlyList<FilterOptionDefinition> LegacyFilterDefinitions = new List<FilterOptionDefinition>()
{
FilterOptionDefinition.AuthorFilter,
FilterOptionDefinition.BaselineFilter,
FilterOptionDefinition.LanguageFilter,
FilterOptionDefinition.TypeFilter,
FilterOptionDefinition.TagFilter,
FilterOptionDefinition.PackageFilter
};

private readonly string _commandName;

internal NewCommand(string commandName, ITemplateEngineHost host, ITelemetryLogger telemetryLogger, NewCommandCallbacks callbacks) : base(host, telemetryLogger, callbacks, commandName, LocalizableStrings.CommandDescription)
Expand All @@ -24,6 +34,20 @@ internal NewCommand(string commandName, ITemplateEngineHost host, ITelemetryLogg
this.AddArgument(RemainingArguments);
this.AddOption(HelpOption);

//legacy options
Dictionary<FilterOptionDefinition, Option> options = new Dictionary<FilterOptionDefinition, Option>();
foreach (var filterDef in LegacyFilterDefinitions)
{
options[filterDef] = filterDef.OptionFactory().AsHidden();
this.AddOption(options[filterDef]);
}
LegacyFilters = options;

this.AddOption(InteractiveOption);
this.AddOption(AddSourceOption);
this.AddOption(ColumnsAllOption);
this.AddOption(ColumnsOption);

this.TreatUnmatchedTokensAsErrors = true;

this.Add(new InstantiateCommand(host, telemetryLogger, callbacks));
Expand All @@ -36,14 +60,11 @@ internal NewCommand(string commandName, ITemplateEngineHost host, ITelemetryLogg
this.Add(new LegacyUpdateApplyCommand(this, host, telemetryLogger, callbacks));
this.Add(new UpdateCommand(this, host, telemetryLogger, callbacks));

this.Add(new SearchCommand(this, host, telemetryLogger, callbacks));
this.Add(new LegacySearchCommand(this, host, telemetryLogger, callbacks));

//yield return (host, telemetryLogger, callbacks) => new ListCommand(host, telemetryLogger, callbacks);
//yield return (host, telemetryLogger, callbacks) => new SearchCommand(host, telemetryLogger, callbacks);
//yield return (host, telemetryLogger, callbacks) => new UpdateCommand(host, telemetryLogger, callbacks);
//yield return (host, telemetryLogger, callbacks) => new AliasCommand(host, telemetryLogger, callbacks);

//legacy options
this.AddOption(InteractiveOption);
this.AddOption(AddSourceOption);
}

internal Argument<string> ShortNameArgument { get; } = new Argument<string>("template-short-name")
Expand All @@ -59,11 +80,20 @@ internal NewCommand(string commandName, ITemplateEngineHost host, ITelemetryLogg
internal Option<bool> HelpOption { get; } = new Option<bool>(new string[] { "-h", "--help", "-?" });

#region Legacy Options
internal Option<bool> InteractiveOption { get; } = SharedOptionsFactory.GetInteractiveOption().AsHidden();

internal virtual Option<bool> InteractiveOption { get; } = SharedOptionsFactory.GetInteractiveOption().AsHidden();
internal Option<IReadOnlyList<string>> AddSourceOption { get; } = SharedOptionsFactory.GetAddSourceOption().AsHidden().DisableAllowMultipleArgumentsPerToken();

internal virtual Option<IReadOnlyList<string>> AddSourceOption { get; } = SharedOptionsFactory.GetAddSourceOption().AsHidden().DisableAllowMultipleArgumentsPerToken();
internal Option<bool> ColumnsAllOption { get; } = SharedOptionsFactory.GetColumnsAllOption().AsHidden();

internal Option<IReadOnlyList<string>> ColumnsOption { get; } = SharedOptionsFactory.GetColumnsOption().AsHidden().DisableAllowMultipleArgumentsPerToken();

internal IReadOnlyDictionary<FilterOptionDefinition, Option> LegacyFilters { get; }

internal Option GetLegacyFilterOption(FilterOptionDefinition def)
{
return LegacyFilters[def];
}
#endregion

protected override IEnumerable<string> GetSuggestions(NewCommandArgs args, IEngineEnvironmentSettings environmentSettings, string? textToMatch)
Expand Down
Loading

0 comments on commit 5aa2568

Please sign in to comment.