diff --git a/src/Microsoft.TemplateEngine.Cli/CliTemplateInfo.cs b/src/Microsoft.TemplateEngine.Cli/CliTemplateInfo.cs
new file mode 100644
index 00000000000..0cbd3610e4d
--- /dev/null
+++ b/src/Microsoft.TemplateEngine.Cli/CliTemplateInfo.cs
@@ -0,0 +1,108 @@
+// 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 Microsoft.TemplateEngine.Abstractions;
+
+namespace Microsoft.TemplateEngine.Cli
+{
+ ///
+ /// + .
+ ///
+ internal class CliTemplateInfo : ITemplateInfo
+ {
+ private readonly ITemplateInfo _templateInfo;
+ private readonly HostSpecificTemplateData _cliData;
+
+ internal CliTemplateInfo(ITemplateInfo templateInfo, HostSpecificTemplateData cliData)
+ {
+ _templateInfo = templateInfo ?? throw new ArgumentNullException(nameof(templateInfo));
+ _cliData = cliData ?? throw new ArgumentNullException(nameof(cliData));
+ }
+
+ public string? Author => _templateInfo.Author;
+
+ public string? Description => _templateInfo.Description;
+
+ public IReadOnlyList Classifications => _templateInfo.Classifications;
+
+ public string? DefaultName => _templateInfo.DefaultName;
+
+ public string Identity => _templateInfo.Identity;
+
+ public Guid GeneratorId => _templateInfo.GeneratorId;
+
+ public string? GroupIdentity => _templateInfo.GroupIdentity;
+
+ public int Precedence => _templateInfo.Precedence;
+
+ public string Name => _templateInfo.Name;
+
+ [Obsolete]
+ public string ShortName => _templateInfo.ShortName;
+
+ [Obsolete]
+ public IReadOnlyDictionary Tags => _templateInfo.Tags;
+
+ public IReadOnlyDictionary TagsCollection => _templateInfo.TagsCollection;
+
+ [Obsolete]
+ public IReadOnlyDictionary CacheParameters => _templateInfo.CacheParameters;
+
+ public IReadOnlyList Parameters => _templateInfo.Parameters;
+
+ public string MountPointUri => _templateInfo.MountPointUri;
+
+ public string ConfigPlace => _templateInfo.ConfigPlace;
+
+ public string? LocaleConfigPlace => _templateInfo.LocaleConfigPlace;
+
+ public string? HostConfigPlace => _templateInfo.HostConfigPlace;
+
+ public string? ThirdPartyNotices => _templateInfo.ThirdPartyNotices;
+
+ public IReadOnlyDictionary BaselineInfo => _templateInfo.BaselineInfo;
+
+ [Obsolete]
+ public bool HasScriptRunningPostActions { get => _templateInfo.HasScriptRunningPostActions; set => _templateInfo.HasScriptRunningPostActions = value; }
+
+ public IReadOnlyList ShortNameList => _templateInfo.ShortNameList;
+
+ internal HostSpecificTemplateData CliData => _cliData;
+
+ internal bool IsHidden => _cliData.IsHidden;
+
+ internal static IEnumerable FromTemplateInfo(IEnumerable templateInfos, IHostSpecificDataLoader hostSpecificDataLoader)
+ {
+ if (templateInfos is null)
+ {
+ throw new ArgumentNullException(nameof(templateInfos));
+ }
+
+ if (hostSpecificDataLoader is null)
+ {
+ throw new ArgumentNullException(nameof(hostSpecificDataLoader));
+ }
+
+ return templateInfos.Select(templateInfo => new CliTemplateInfo(templateInfo, hostSpecificDataLoader.ReadHostSpecificTemplateData(templateInfo)));
+ }
+
+ internal IEnumerable GetParameters()
+ {
+ HashSet processedParameters = new HashSet();
+ List parameters = new List();
+
+ foreach (ITemplateParameter parameter in Parameters.Where(param => param.Type == "parameter"))
+ {
+ if (!processedParameters.Add(parameter.Name))
+ {
+ //TODO:
+ throw new Exception($"Template {Identity} defines {parameter.Name} twice.");
+ }
+ parameters.Add(new CliTemplateParameter(parameter, CliData));
+ }
+ return parameters;
+ }
+ }
+}
diff --git a/src/Microsoft.TemplateEngine.Cli/CliTemplateParameter.cs b/src/Microsoft.TemplateEngine.Cli/CliTemplateParameter.cs
new file mode 100644
index 00000000000..1b91b74a761
--- /dev/null
+++ b/src/Microsoft.TemplateEngine.Cli/CliTemplateParameter.cs
@@ -0,0 +1,107 @@
+// 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 Microsoft.TemplateEngine.Abstractions;
+
+namespace Microsoft.TemplateEngine.Cli
+{
+ internal enum ParameterType
+ {
+ Boolean,
+ Choice,
+ Float,
+ Integer,
+ Hex,
+ String
+ }
+
+ internal class CliTemplateParameter
+ {
+ private List _shortNameOverrides = new List();
+
+ private List _longNameOverrides = new List();
+
+ private Dictionary _choices = new Dictionary(StringComparer.OrdinalIgnoreCase);
+
+ internal CliTemplateParameter(ITemplateParameter parameter, HostSpecificTemplateData data)
+ {
+ Name = parameter.Name;
+ Description = parameter.Description ?? string.Empty;
+ Type = ParseType(parameter.DataType);
+ DefaultValue = parameter.DefaultValue;
+ DefaultIfOptionWithoutValue = parameter.DefaultIfOptionWithoutValue ?? string.Empty;
+ IsRequired = parameter.Priority == TemplateParameterPriority.Required && parameter.DefaultValue == null;
+ IsHidden = parameter.Priority == TemplateParameterPriority.Implicit || data.HiddenParameterNames.Contains(parameter.Name);
+
+ if (parameter.Choices != null)
+ {
+ _choices = parameter.Choices.ToDictionary(kvp => kvp.Key, kvp => kvp.Value, StringComparer.OrdinalIgnoreCase);
+ }
+ if (data.ShortNameOverrides.ContainsKey(parameter.Name))
+ {
+ _shortNameOverrides.Add(data.ShortNameOverrides[parameter.Name]);
+ }
+ if (data.LongNameOverrides.ContainsKey(parameter.Name))
+ {
+ _longNameOverrides.Add(data.LongNameOverrides[parameter.Name]);
+ }
+ }
+
+ ///
+ /// Unit test constructor.
+ ///
+ internal CliTemplateParameter(
+ string name,
+ ParameterType type = ParameterType.String,
+ IEnumerable? shortNameOverrides = null,
+ IEnumerable? longNameOverrides = null,
+ int precedence = 0)
+ {
+ Name = name;
+ Type = type;
+ _shortNameOverrides = shortNameOverrides?.ToList() ?? new List();
+ _longNameOverrides = longNameOverrides?.ToList() ?? new List();
+
+ Description = string.Empty;
+ DefaultValue = string.Empty;
+ DefaultIfOptionWithoutValue = string.Empty;
+ }
+
+ internal string Name { get; private set; }
+
+ internal string Description { get; private set; }
+
+ internal ParameterType Type { get; private set; }
+
+ internal string? DefaultValue { get; private set; }
+
+ internal bool IsRequired { get; private set; }
+
+ internal bool IsHidden { get; private set; }
+
+ internal IReadOnlyDictionary? Choices => _choices;
+
+ internal IReadOnlyList ShortNameOverrides => _shortNameOverrides;
+
+ internal IReadOnlyList LongNameOverrides => _longNameOverrides;
+
+ //TODO: decide if we handle it
+ internal string DefaultIfOptionWithoutValue { get; private set; }
+
+ private static ParameterType ParseType(string dataType)
+ {
+ return dataType switch
+ {
+ "bool" => ParameterType.Boolean,
+ "boolean" => ParameterType.Boolean,
+ "choice" => ParameterType.Choice,
+ "float" => ParameterType.Float,
+ "int" => ParameterType.Integer,
+ "integer" => ParameterType.Integer,
+ _ => ParameterType.String
+ };
+ }
+ }
+}
diff --git a/src/Microsoft.TemplateEngine.Cli/CommandParsing/AliasAssignmentCoordinator.cs b/src/Microsoft.TemplateEngine.Cli/CommandParsing/AliasAssignmentCoordinator.cs
deleted file mode 100644
index d48f1e627a7..00000000000
--- a/src/Microsoft.TemplateEngine.Cli/CommandParsing/AliasAssignmentCoordinator.cs
+++ /dev/null
@@ -1,187 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using Microsoft.TemplateEngine.Abstractions;
-
-namespace Microsoft.TemplateEngine.Cli.CommandParsing
-{
- internal class AliasAssignmentCoordinator
- {
- private IReadOnlyList _parameterDefinitions;
- private IDictionary _longNameOverrides;
- private IDictionary _shortNameOverrides;
- private HashSet _takenAliases;
- private Dictionary _longAssignments;
- private Dictionary _shortAssignments;
- private HashSet _invalidParams;
- private bool _calculatedAssignments;
-
- internal AliasAssignmentCoordinator(IReadOnlyList parameterDefinitions, IDictionary longNameOverrides, IDictionary shortNameOverrides, HashSet takenAliases)
- {
- _parameterDefinitions = parameterDefinitions;
- _longNameOverrides = longNameOverrides;
- _shortNameOverrides = shortNameOverrides;
- _takenAliases = takenAliases;
- _longAssignments = new Dictionary();
- _shortAssignments = new Dictionary();
- _invalidParams = new HashSet();
- _calculatedAssignments = false;
- }
-
- internal IReadOnlyDictionary LongNameAssignments
- {
- get
- {
- EnsureAliasAssignments();
- return _longAssignments;
- }
- }
-
- internal IReadOnlyDictionary ShortNameAssignments
- {
- get
- {
- EnsureAliasAssignments();
- return _shortAssignments;
- }
- }
-
- internal HashSet InvalidParams
- {
- get
- {
- EnsureAliasAssignments();
- return _invalidParams;
- }
- }
-
- internal HashSet TakenAliases
- {
- get
- {
- EnsureAliasAssignments();
- return _takenAliases;
- }
- }
-
- private void EnsureAliasAssignments()
- {
- if (_calculatedAssignments)
- {
- return;
- }
-
- Dictionary> aliasAssignments = new Dictionary>();
- Dictionary paramNamesNeedingAssignment = _parameterDefinitions.Where(x => x.Priority != TemplateParameterPriority.Implicit)
- .ToDictionary(x => x.Name, x => x);
-
- SetupAssignmentsFromLongOverrides(paramNamesNeedingAssignment);
- SetupAssignmentsFromShortOverrides(paramNamesNeedingAssignment);
- SetupAssignmentsWithoutOverrides(paramNamesNeedingAssignment);
-
- _calculatedAssignments = true;
- }
-
- private void SetupAssignmentsFromLongOverrides(IReadOnlyDictionary paramNamesNeedingAssignment)
- {
- foreach (KeyValuePair canonicalAndLong in _longNameOverrides.Where(x => paramNamesNeedingAssignment.ContainsKey(x.Key)))
- {
- string canonical = canonicalAndLong.Key;
- string longOverride = canonicalAndLong.Value;
- if (CommandAliasAssigner.TryAssignAliasesForParameter((x) => _takenAliases.Contains(x), canonical, longOverride, null, out IReadOnlyList assignedAliases))
- {
- // only deal with the long here, ignore the short for now
- string longParam = assignedAliases.FirstOrDefault(x => x.StartsWith("--"));
- if (!string.IsNullOrEmpty(longParam))
- {
- _longAssignments.Add(canonical, longParam);
- _takenAliases.Add(longParam);
- }
- }
- else
- {
- _invalidParams.Add(canonical);
- }
- }
- }
-
- private void SetupAssignmentsFromShortOverrides(IReadOnlyDictionary paramNamesNeedingAssignment)
- {
- foreach (KeyValuePair canonicalAndShort in _shortNameOverrides.Where(x => paramNamesNeedingAssignment.ContainsKey(x.Key)))
- {
- string canonical = canonicalAndShort.Key;
- string shortOverride = canonicalAndShort.Value;
-
- if (shortOverride == string.Empty)
- {
- // it was explicitly empty string in the host file. If it wasn't specified, it'll be null
- // this means there should be no short version
- continue;
- }
-
- if (CommandAliasAssigner.TryAssignAliasesForParameter((x) => _takenAliases.Contains(x), canonical, null, shortOverride, out IReadOnlyList assignedAliases))
- {
- string shortParam = assignedAliases.FirstOrDefault(x => x.StartsWith("-") && !x.StartsWith("--"));
- if (!string.IsNullOrEmpty(shortParam))
- {
- _shortAssignments.Add(canonical, shortParam);
- _takenAliases.Add(shortParam);
- }
- }
- else
- {
- _invalidParams.Add(canonical);
- }
- }
- }
-
- private void SetupAssignmentsWithoutOverrides(IReadOnlyDictionary paramNamesNeedingAssignment)
- {
- foreach (ITemplateParameter parameterInfo in paramNamesNeedingAssignment.Values)
- {
- if (_longAssignments.ContainsKey(parameterInfo.Name) && _shortAssignments.ContainsKey(parameterInfo.Name))
- {
- // already fully assigned
- continue;
- }
-
- _longNameOverrides.TryGetValue(parameterInfo.Name, out string longOverride);
- _shortNameOverrides.TryGetValue(parameterInfo.Name, out string shortOverride);
-
- if (CommandAliasAssigner.TryAssignAliasesForParameter((x) => _takenAliases.Contains(x), parameterInfo.Name, longOverride, shortOverride, out IReadOnlyList assignedAliases))
- {
- if (shortOverride != string.Empty)
- {
- // explicit empty string in the host file means there should be no short name.
- // but thats not the case here.
- if (!_shortAssignments.ContainsKey(parameterInfo.Name))
- {
- // still needs a short version
- string shortParam = assignedAliases.FirstOrDefault(x => x.StartsWith("-") && !x.StartsWith("--"));
- if (!string.IsNullOrEmpty(shortParam))
- {
- _shortAssignments.Add(parameterInfo.Name, shortParam);
- _takenAliases.Add(shortParam);
- }
- }
- }
-
- if (!_longAssignments.ContainsKey(parameterInfo.Name))
- {
- // still needs a long version
- string longParam = assignedAliases.FirstOrDefault(x => x.StartsWith("--"));
- if (!string.IsNullOrEmpty(longParam))
- {
- _longAssignments.Add(parameterInfo.Name, longParam);
- _takenAliases.Add(longParam);
- }
- }
- }
- else
- {
- _invalidParams.Add(parameterInfo.Name);
- }
- }
- }
- }
-}
diff --git a/src/Microsoft.TemplateEngine.Cli/CommandParsing/CommandParserSupport.cs b/src/Microsoft.TemplateEngine.Cli/CommandParsing/CommandParserSupport.cs
index 14e1ac2a932..08d584c1a6a 100644
--- a/src/Microsoft.TemplateEngine.Cli/CommandParsing/CommandParserSupport.cs
+++ b/src/Microsoft.TemplateEngine.Cli/CommandParsing/CommandParserSupport.cs
@@ -53,15 +53,15 @@ private static Option[] NewCommandVisibleArgs
.ZeroOrOneArgument()
.And(ArgumentCannotStartWithDashRule)
.With(LocalizableStrings.ListsTemplates, "PARTIAL_NAME")),
- Create.Option("-n|--name", LocalizableStrings.NameOfOutput, Accept.ExactlyOneArgument()),
- Create.Option("-o|--output", LocalizableStrings.OutputPath, Accept.ExactlyOneArgument()),
+ Create.Option("-n|--name", LocalizableStrings.OptionDescriptionName, Accept.ExactlyOneArgument()),
+ Create.Option("-o|--output", LocalizableStrings.OptionDescriptionOutput, Accept.ExactlyOneArgument()),
Create.Option("-i|--install", LocalizableStrings.InstallHelp, Accept.OneOrMoreArguments()),
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.OptionDescriptionTypeFilter, Accept.ExactlyOneArgument()),
- Create.Option("--dry-run", LocalizableStrings.DryRunDescription, Accept.NoArguments()),
- Create.Option("--force", LocalizableStrings.ForcesTemplateCreation, Accept.NoArguments()),
+ Create.Option("--dry-run", LocalizableStrings.OptionDescriptionDryRun, Accept.NoArguments()),
+ Create.Option("--force", LocalizableStrings.OptionDescriptionForce, Accept.NoArguments()),
Create.Option("-lang|--language", LocalizableStrings.OptionDescriptionLanguageFilter, Accept.ExactlyOneArgument()),
Create.Option("--update-check", LocalizableStrings.UpdateCheckCommandHelp, Accept.NoArguments()),
Create.Option("--update-apply", LocalizableStrings.UpdateApplyCommandHelp, Accept.NoArguments()),
@@ -147,47 +147,48 @@ internal static Command CreateNewCommandWithArgsForTemplate(
HashSet initiallyTakenAliases = ArgsForBuiltInCommands;
Dictionary> canonicalToVariantMap = new Dictionary>();
- AliasAssignmentCoordinator assignmentCoordinator = new AliasAssignmentCoordinator(parameterDefinitions, longNameOverrides, shortNameOverrides, initiallyTakenAliases);
-
- if (assignmentCoordinator.InvalidParams.Count > 0)
- {
- string unusableDisplayList = string.Join(", ", assignmentCoordinator.InvalidParams);
- throw new Exception($"Template is malformed. The following parameter names are invalid: {unusableDisplayList}");
- }
-
- foreach (ITemplateParameter parameter in parameterDefinitions.Where(x => x.Priority != TemplateParameterPriority.Implicit))
- {
- Option option;
- IList aliasesForParam = new List();
-
- if (assignmentCoordinator.LongNameAssignments.TryGetValue(parameter.Name, out string longVersion))
- {
- aliasesForParam.Add(longVersion);
- }
-
- if (assignmentCoordinator.ShortNameAssignments.TryGetValue(parameter.Name, out string shortVersion))
- {
- aliasesForParam.Add(shortVersion);
- }
-
- if (!string.IsNullOrEmpty(parameter.DefaultIfOptionWithoutValue))
- {
- // This switch can be provided with or without a value.
- // If the user doesn't specify a value, it gets the value of DefaultIfOptionWithoutValue
- option = Create.Option(string.Join("|", aliasesForParam), parameter.Description, Accept.ZeroOrOneArgument());
- }
- else
- {
- // User must provide a value if this switch is specified.
- option = Create.Option(string.Join("|", aliasesForParam), parameter.Description, Accept.ExactlyOneArgument());
- }
-
- paramOptionList.Add(option); // add the option
- canonicalToVariantMap.Add(parameter.Name, aliasesForParam.ToList()); // map the template canonical name to its aliases.
- }
-
- templateParamMap = canonicalToVariantMap;
- return GetNewCommandForTemplate(commandName, templateName, NewCommandVisibleArgs, NewCommandHiddenArgs, DebuggingCommandArgs, paramOptionList.ToArray());
+ throw new NotImplementedException();
+ //AliasAssignmentCoordinator assignmentCoordinator = new AliasAssignmentCoordinator(parameterDefinitions, longNameOverrides, shortNameOverrides, initiallyTakenAliases);
+
+ //if (assignmentCoordinator.InvalidParams.Count > 0)
+ //{
+ // string unusableDisplayList = string.Join(", ", assignmentCoordinator.InvalidParams);
+ // throw new Exception($"Template is malformed. The following parameter names are invalid: {unusableDisplayList}");
+ //}
+
+ //foreach (ITemplateParameter parameter in parameterDefinitions.Where(x => x.Priority != TemplateParameterPriority.Implicit))
+ //{
+ // Option option;
+ // IList aliasesForParam = new List();
+
+ // if (assignmentCoordinator.LongNameAssignments.TryGetValue(parameter.Name, out string longVersion))
+ // {
+ // aliasesForParam.Add(longVersion);
+ // }
+
+ // if (assignmentCoordinator.ShortNameAssignments.TryGetValue(parameter.Name, out string shortVersion))
+ // {
+ // aliasesForParam.Add(shortVersion);
+ // }
+
+ // if (!string.IsNullOrEmpty(parameter.DefaultIfOptionWithoutValue))
+ // {
+ // // This switch can be provided with or without a value.
+ // // If the user doesn't specify a value, it gets the value of DefaultIfOptionWithoutValue
+ // option = Create.Option(string.Join("|", aliasesForParam), parameter.Description, Accept.ZeroOrOneArgument());
+ // }
+ // else
+ // {
+ // // User must provide a value if this switch is specified.
+ // option = Create.Option(string.Join("|", aliasesForParam), parameter.Description, Accept.ExactlyOneArgument());
+ // }
+
+ // paramOptionList.Add(option); // add the option
+ // canonicalToVariantMap.Add(parameter.Name, aliasesForParam.ToList()); // map the template canonical name to its aliases.
+ //}
+
+ //templateParamMap = canonicalToVariantMap;
+ //return GetNewCommandForTemplate(commandName, templateName, NewCommandVisibleArgs, NewCommandHiddenArgs, DebuggingCommandArgs, paramOptionList.ToArray());
}
internal static Command CreateNewCommandWithoutTemplateInfo(string commandName)
diff --git a/src/Microsoft.TemplateEngine.Cli/Commands/AliasAssignmentCoordinator.cs b/src/Microsoft.TemplateEngine.Cli/Commands/AliasAssignmentCoordinator.cs
new file mode 100644
index 00000000000..3706dfd5ccf
--- /dev/null
+++ b/src/Microsoft.TemplateEngine.Cli/Commands/AliasAssignmentCoordinator.cs
@@ -0,0 +1,195 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+namespace Microsoft.TemplateEngine.Cli.Commands
+{
+ internal class AliasAssignmentCoordinator
+ {
+ internal static IEnumerable<(CliTemplateParameter Parameter, IEnumerable Aliases, IEnumerable Errors)> AssignAliasesForParameter(IEnumerable parameters, HashSet takenAliases)
+ {
+ List<(CliTemplateParameter Parameter, IEnumerable Aliases, IEnumerable Errors)> result = new();
+
+ List predefinedLongOverrides = parameters.SelectMany(p => p.LongNameOverrides).Where(n => !string.IsNullOrEmpty(n)).Select(n => $"--{n}").ToList();
+ List predefinedShortOverrides = parameters.SelectMany(p => p.ShortNameOverrides).Where(n => !string.IsNullOrEmpty(n)).Select(n => $"-{n}").ToList();
+
+ Func isAliasTaken = (s) => takenAliases.Contains(s);
+ Func isLongNamePredefined = (s) => predefinedLongOverrides.Contains(s);
+ Func isShortNamePredefined = (s) => predefinedShortOverrides.Contains(s);
+
+ foreach (var parameter in parameters)
+ {
+ List aliases = new List();
+ List errors = new List();
+ if (parameter.Name.Contains(':'))
+ {
+ // Colon is reserved, template param names cannot have any.
+ errors.Add($"Parameter name '{parameter.Name}' contains colon, which is forbidden.");
+ result.Add((parameter, aliases, errors));
+ continue;
+ }
+
+ HandleLongOverrides(takenAliases, aliases, errors, isAliasTaken, isLongNamePredefined, parameter);
+ HandleShortOverrides(takenAliases, aliases, errors, isAliasTaken, parameter);
+
+ //if there is already short name override defined, do not generate new one
+ if (parameter.ShortNameOverrides.Any())
+ {
+ result.Add((parameter, aliases, errors));
+ continue;
+ }
+
+ GenerateShortName(takenAliases, aliases, errors, isAliasTaken, isShortNamePredefined, parameter);
+ result.Add((parameter, aliases, errors));
+ }
+ return result;
+ }
+
+ private static void HandleShortOverrides(
+ HashSet takenAliases,
+ List aliases,
+ List errors,
+ Func isAliasTaken,
+ CliTemplateParameter parameter)
+ {
+ foreach (string shortNameOverride in parameter.ShortNameOverrides)
+ {
+ if (shortNameOverride == string.Empty)
+ {
+ // it was explicitly empty string in the host file.
+ continue;
+ }
+ if (!string.IsNullOrEmpty(shortNameOverride))
+ {
+ // short name starting point was explicitly specified
+ string fullShortNameOverride = "-" + shortNameOverride;
+ if (!isAliasTaken(shortNameOverride))
+ {
+ aliases.Add(fullShortNameOverride);
+ takenAliases.Add(fullShortNameOverride);
+ continue;
+ }
+
+ //if taken, we append prefix
+ string qualifiedShortNameOverride = "-p:" + shortNameOverride;
+ if (!isAliasTaken(qualifiedShortNameOverride))
+ {
+ aliases.Add(qualifiedShortNameOverride);
+ takenAliases.Add(qualifiedShortNameOverride);
+ continue;
+ }
+ errors.Add($"Failed to assign short option name from {parameter.Name}, tried: '{fullShortNameOverride}'; '{qualifiedShortNameOverride}.'");
+ }
+ }
+ }
+
+ private static void HandleLongOverrides(
+ HashSet takenAliases,
+ List aliases,
+ List errors,
+ Func isAliasTaken,
+ Func isLongNamePredefined,
+ CliTemplateParameter parameter)
+ {
+ bool noLongOverrideDefined = false;
+ IEnumerable longNameOverrides = parameter.LongNameOverrides;
+
+ //if no long override define, we use parameter name
+ if (!longNameOverrides.Any())
+ {
+ longNameOverrides = new[] { parameter.Name };
+ noLongOverrideDefined = true;
+ }
+
+ foreach (string longName in longNameOverrides)
+ {
+ string optionName = "--" + longName;
+ if ((!noLongOverrideDefined && !isAliasTaken(optionName))
+ //if we use parameter name, we should also check if there is any other parameter which defines this long name.
+ //in case it is, then we should give precedence to other parameter to use it.
+ || (noLongOverrideDefined && !isAliasTaken(optionName) && !isLongNamePredefined(optionName)))
+ {
+ aliases.Add(optionName);
+ takenAliases.Add(optionName);
+ continue;
+ }
+
+ // if paramater name is taken
+ optionName = "--param:" + longName;
+ if (!isAliasTaken(optionName))
+ {
+ aliases.Add(optionName);
+ takenAliases.Add(optionName);
+ continue;
+ }
+ errors.Add($"Failed to assign long option name from {parameter.Name}, tried: '--{longName}'; '--param:{longName}.'");
+ }
+ }
+
+ private static void GenerateShortName(
+ HashSet takenAliases,
+ List aliases,
+ List errors,
+ Func isAliasTaken,
+ Func isShortNamePredefined,
+ CliTemplateParameter parameter)
+ {
+ //use long override as base, if exists
+ string flagFullText = parameter.LongNameOverrides.Count > 0 ? parameter.LongNameOverrides[0] : parameter.Name;
+
+ // try to generate un-prefixed name, if not taken.
+ string shortName = GetFreeShortName(s => isAliasTaken(s) || (isShortNamePredefined(s)), flagFullText);
+ if (!isAliasTaken(shortName))
+ {
+ aliases.Add(shortName);
+ takenAliases.Add(shortName);
+ return;
+ }
+
+ // try to generate prefixed name, as the fallback
+ string qualifiedShortName = GetFreeShortName(s => isAliasTaken(s) || (isShortNamePredefined(s)), flagFullText, "p:");
+ if (!isAliasTaken(qualifiedShortName))
+ {
+ aliases.Add(qualifiedShortName);
+ return;
+ }
+ errors.Add($"Failed to assign short option name from {parameter.Name}, tried: '{shortName}'; '{qualifiedShortName}.'");
+ }
+
+ private static string GetFreeShortName(Func isAliasTaken, string name, string prefix = "")
+ {
+ string[] parts = name.Split(new[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
+ string[] buckets = new string[parts.Length];
+
+ for (int i = 0; i < buckets.Length; ++i)
+ {
+ buckets[i] = parts[i].Substring(0, 1);
+ }
+
+ int lastBucket = parts.Length - 1;
+ while (isAliasTaken("-" + prefix + string.Join("", buckets)))
+ {
+ //Find the next thing we can take a character from
+ bool first = true;
+ int end = (lastBucket + 1) % parts.Length;
+ int i = (lastBucket + 1) % parts.Length;
+ for (; first || i != end; first = false, i = (i + 1) % parts.Length)
+ {
+ if (parts[i].Length > buckets[i].Length)
+ {
+ buckets[i] = parts[i].Substring(0, buckets[i].Length + 1);
+ break;
+ }
+ }
+
+ if (i == end)
+ {
+ break;
+ }
+ }
+
+ return "-" + prefix + string.Join("", buckets);
+ }
+ }
+}
diff --git a/src/Microsoft.TemplateEngine.Cli/Commands/BaseCommand.cs b/src/Microsoft.TemplateEngine.Cli/Commands/BaseCommand.cs
index 9d15d891e90..2c05bcc8419 100644
--- a/src/Microsoft.TemplateEngine.Cli/Commands/BaseCommand.cs
+++ b/src/Microsoft.TemplateEngine.Cli/Commands/BaseCommand.cs
@@ -17,8 +17,10 @@ namespace Microsoft.TemplateEngine.Cli.Commands
{
internal abstract class BaseCommand : Command
{
- protected BaseCommand(string name, string? description = null) : base(name, description)
+ protected BaseCommand(ITelemetryLogger logger, NewCommandCallbacks callbacks, string name, string? description = null) : base(name, description)
{
+ TelemetryLogger = logger;
+ Callbacks = callbacks;
}
internal Option DebugCustomSettingsLocationOption { get; } = new("--debug:custom-hive", "Sets custom settings location")
@@ -50,21 +52,30 @@ protected BaseCommand(string name, string? description = null) : base(name, desc
{
IsHidden = true
};
+
+ internal ITelemetryLogger TelemetryLogger { get; }
+
+ internal NewCommandCallbacks Callbacks { get; }
}
internal abstract class BaseCommand : BaseCommand, ICommandHandler where TArgs : GlobalArgs
{
private static readonly Guid _entryMutexGuid = new Guid("5CB26FD1-32DB-4F4C-B3DC-49CFD61633D2");
- private readonly ITemplateEngineHost _host;
+ private readonly ITemplateEngineHost? _host;
internal BaseCommand(ITemplateEngineHost host, ITelemetryLogger logger, NewCommandCallbacks callbacks, string name, string? description = null)
- : base(name, description)
+ : this(logger, callbacks, name, description)
{
_host = host;
- TelemetryLogger = logger;
- Callbacks = callbacks;
this.Handler = this;
+ }
+
+ //command called via this constructor is not invokable
+ internal BaseCommand(BaseCommand parent, string name, string? description = null) : this(parent.TelemetryLogger, parent.Callbacks, name, description) { }
+
+ private BaseCommand(ITelemetryLogger logger, NewCommandCallbacks callbacks, string name, string? description = null) : base(logger, callbacks, name, description)
+ {
this.AddOption(DebugCustomSettingsLocationOption);
this.AddOption(DebugVirtualizeSettingsOption);
this.AddOption(DebugAttachOption);
@@ -73,10 +84,6 @@ internal BaseCommand(ITemplateEngineHost host, ITelemetryLogger logger, NewComma
this.AddOption(DebugShowConfigOption);
}
- internal ITelemetryLogger TelemetryLogger { get; }
-
- internal NewCommandCallbacks Callbacks { get; }
-
public async Task InvokeAsync(InvocationContext context)
{
TArgs args = ParseContext(context.ParseResult);
@@ -146,19 +153,28 @@ protected virtual IEnumerable GetSuggestions(TArgs args, IEngineEnvironm
return base.GetSuggestions(args.ParseResult, textToMatch);
}
- protected IEngineEnvironmentSettings CreateEnvironmentSettings(TArgs args)
+ private IEngineEnvironmentSettings CreateEnvironmentSettings(TArgs args)
{
- string? outputPath = (args as InstantiateCommandArgs)?.OutputPath;
+ //TODO: replace with reparse
+ //string? outputPath = (args as InstantiateCommandArgs)?.OutputPath;
+
+ if (_host is null)
+ {
+ throw new ArgumentException("The method should not be used if host is not available.");
+ }
IEngineEnvironmentSettings environmentSettings = new EngineEnvironmentSettings(
- new CliTemplateEngineHost(_host, outputPath),
+ new CliTemplateEngineHost(_host, string.Empty),
+ //new CliTemplateEngineHost(_host, outputPath),
settingsLocation: args.DebugCustomSettingsLocation,
virtualizeSettings: args.DebugVirtualizeSettings,
environment: new CliEnvironment());
return environmentSettings;
}
+#pragma warning disable SA1202 // Elements should be ordered by access
protected abstract Task ExecuteAsync(TArgs args, IEngineEnvironmentSettings environmentSettings, InvocationContext context);
+#pragma warning restore SA1202 // Elements should be ordered by access
protected abstract TArgs ParseContext(ParseResult parseResult);
diff --git a/src/Microsoft.TemplateEngine.Cli/Commands/Extensions.cs b/src/Microsoft.TemplateEngine.Cli/Commands/Extensions.cs
new file mode 100644
index 00000000000..8b45b6528d9
--- /dev/null
+++ b/src/Microsoft.TemplateEngine.Cli/Commands/Extensions.cs
@@ -0,0 +1,44 @@
+// 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 System.CommandLine.Parsing;
+
+namespace Microsoft.TemplateEngine.Cli.Commands
+{
+ internal static class Extensions
+ {
+ internal static string? GetValueForOptionOrNull(this ParseResult parseResult, IOption option)
+ {
+ OptionResult? result = parseResult.FindResultFor(option);
+ if (result == null)
+ {
+ return null;
+ }
+ if (result.Token is null)
+ {
+ return null;
+ }
+ return result.GetValueOrDefault()?.ToString();
+ }
+
+ ///
+ /// Gets name of from .
+ /// might be result for subcommand, then method will traverse up until is found.
+ ///
+ ///
+ ///
+ internal static string GetNewCommandName(this ParseResult parseResult)
+ {
+ var command = parseResult.CommandResult.Command;
+
+ while (command != null && command is not NewCommand)
+ {
+ command = (parseResult.CommandResult.Parent as CommandResult)?.Command;
+ }
+ return command?.Name ?? string.Empty;
+ }
+ }
+}
diff --git a/src/Microsoft.TemplateEngine.Cli/Commands/GlobalArgs.cs b/src/Microsoft.TemplateEngine.Cli/Commands/GlobalArgs.cs
index a84a4fdae94..18662d14175 100644
--- a/src/Microsoft.TemplateEngine.Cli/Commands/GlobalArgs.cs
+++ b/src/Microsoft.TemplateEngine.Cli/Commands/GlobalArgs.cs
@@ -17,7 +17,7 @@ public GlobalArgs(BaseCommand command, ParseResult parseResult)
DebugReinit = parseResult.GetValueForOption(command.DebugReinitOption);
DebugRebuildCache = parseResult.GetValueForOption(command.DebugRebuildCacheOption);
DebugShowConfig = parseResult.GetValueForOption(command.DebugShowConfigOption);
- CommandName = GetNewCommandName(parseResult);
+ CommandName = parseResult.GetNewCommandName();
ParseResult = parseResult;
}
@@ -42,15 +42,5 @@ protected static (bool, IReadOnlyList?) ParseTabularOutputSettings(ITabu
return (parseResult.GetValueForOption(command.ColumnsAllOption), parseResult.GetValueForOption(command.ColumnsOption));
}
- private string GetNewCommandName(ParseResult parseResult)
- {
- var command = parseResult.CommandResult.Command;
-
- while (command != null && command is not NewCommand)
- {
- command = (parseResult.CommandResult.Parent as CommandResult)?.Command;
- }
- return command?.Name ?? string.Empty;
- }
}
}
diff --git a/src/Microsoft.TemplateEngine.Cli/Commands/InstantiateCommand.cs b/src/Microsoft.TemplateEngine.Cli/Commands/InstantiateCommand.cs
index 66da7c26718..9d84f576d0e 100644
--- a/src/Microsoft.TemplateEngine.Cli/Commands/InstantiateCommand.cs
+++ b/src/Microsoft.TemplateEngine.Cli/Commands/InstantiateCommand.cs
@@ -5,42 +5,197 @@
using System.CommandLine;
using System.CommandLine.Invocation;
+using System.CommandLine.IO;
using System.CommandLine.Parsing;
using Microsoft.TemplateEngine.Abstractions;
+using Microsoft.TemplateEngine.Edge.Settings;
namespace Microsoft.TemplateEngine.Cli.Commands
{
internal class InstantiateCommand : BaseCommand
{
- internal InstantiateCommand(ITemplateEngineHost host, ITelemetryLogger logger, NewCommandCallbacks callbacks) : base(host, logger, callbacks, "create")
+ private NewCommand? _parentCommand;
+
+ internal InstantiateCommand(ITemplateEngineHost host, ITelemetryLogger logger, NewCommandCallbacks callbacks) : base(host, logger, callbacks, "create", "TODO")
+ {
+ this.AddArgument(ShortNameArgument);
+ this.AddArgument(RemainingArguments);
+ this.AddOption(HelpOption);
+ }
+
+ private InstantiateCommand(NewCommand parentCommand, string name, string? description = null) : base(parentCommand, name, description)
+ {
+ _parentCommand = parentCommand;
+ this.AddArgument(ShortNameArgument);
+ this.AddArgument(RemainingArguments);
+ this.AddOption(HelpOption);
+ }
+
+ internal Argument ShortNameArgument { get; } = new Argument("template-short-name")
+ {
+ Arity = new ArgumentArity(0, 1)
+ };
+
+ internal Argument RemainingArguments { get; } = new Argument("template-args")
+ {
+ Arity = new ArgumentArity(0, 999)
+ };
+
+ internal Option HelpOption { get; } = new Option(new string[] { "-h", "--help", "-?" })
{
- InstantiateCommandArgs.AddToCommand(this);
+ IsHidden = true
+ };
+ internal static InstantiateCommand FromNewCommand(NewCommand parentCommand)
+ {
+ return new InstantiateCommand(parentCommand, parentCommand.Name, parentCommand.Description);
}
- protected override Task ExecuteAsync(InstantiateCommandArgs args, IEngineEnvironmentSettings environmentSettings, InvocationContext context) => throw new NotImplementedException();
+ internal Task ExecuteAsync(ParseResult parseResult, IEngineEnvironmentSettings environmentSettings, InvocationContext context)
+ {
+ return ExecuteAsync(ParseContext(parseResult), environmentSettings, context);
+ }
+
+ internal HashSet GetTemplateCommand(
+ InstantiateCommandArgs args,
+ IEngineEnvironmentSettings environmentSettings,
+ TemplatePackageManager templatePackageManager,
+ TemplateGroup templateGroup)
+ {
+ foreach (IGrouping templateGrouping in templateGroup.Templates.GroupBy(g => g.Precedence).OrderByDescending(g => g.Key))
+ {
+ HashSet candidates = new HashSet();
+ foreach (CliTemplateInfo template in templateGrouping)
+ {
+ TemplateCommand command = new TemplateCommand(this, environmentSettings, templatePackageManager, templateGroup, template);
+ Parser parser = TemplateParserFactory.CreateParser(command);
+ ParseResult parseResult = parser.Parse(args.RemainingArguments ?? Array.Empty());
+ if (!parseResult.Errors.Any())
+ {
+ candidates.Add(command);
+ }
+ }
+ if (!candidates.Any())
+ {
+ continue;
+ }
+ return candidates;
+ }
+ return new HashSet();
+ }
+
+ protected async override Task ExecuteAsync(InstantiateCommandArgs instantiateArgs, IEngineEnvironmentSettings environmentSettings, InvocationContext context)
+ {
+ if (string.IsNullOrWhiteSpace(instantiateArgs.ShortName) && instantiateArgs.HelpRequested)
+ {
+ context.HelpBuilder.Write(
+ context.ParseResult.CommandResult.Command,
+ StandardStreamWriter.Create(context.Console.Out),
+ context.ParseResult);
+ return NewCommandStatus.Success;
+ }
+ using TemplatePackageManager templatePackageManager = new TemplatePackageManager(environmentSettings);
+ var hostSpecificDataLoader = new HostSpecificDataLoader(environmentSettings);
+ if (string.IsNullOrWhiteSpace(instantiateArgs.ShortName))
+ {
+ TemplateListCoordinator templateListCoordinator = new TemplateListCoordinator(
+ environmentSettings,
+ templatePackageManager,
+ hostSpecificDataLoader,
+ TelemetryLogger);
- protected override InstantiateCommandArgs ParseContext(ParseResult parseResult) => throw new NotImplementedException();
+ return await templateListCoordinator.DisplayCommandDescriptionAsync(instantiateArgs, default).ConfigureAwait(false);
+ }
+
+ var templates = await templatePackageManager.GetTemplatesAsync(context.GetCancellationToken()).ConfigureAwait(false);
+ var templateGroups = TemplateGroup.FromTemplateList(CliTemplateInfo.FromTemplateInfo(templates, hostSpecificDataLoader));
+
+ //TODO: decide what to do if there are more than 1 group.
+ var selectedTemplateGroup = templateGroups.FirstOrDefault(template => template.ShortNames.Contains(instantiateArgs.ShortName));
+
+ if (selectedTemplateGroup == null)
+ {
+ Reporter.Error.WriteLine(
+ string.Format(LocalizableStrings.NoTemplatesMatchingInputParameters, instantiateArgs.ShortName).Bold().Red());
+ Reporter.Error.WriteLine();
+
+ Reporter.Error.WriteLine(LocalizableStrings.ListTemplatesCommand);
+ Reporter.Error.WriteCommand(CommandExamples.ListCommandExample(instantiateArgs.CommandName));
+
+ Reporter.Error.WriteLine(LocalizableStrings.SearchTemplatesCommand);
+ Reporter.Error.WriteCommand(CommandExamples.SearchCommandExample(instantiateArgs.CommandName, instantiateArgs.ShortName));
+ Reporter.Error.WriteLine();
+ return NewCommandStatus.NotFound;
+ }
+ return await HandleTemplateInstantationAsync(instantiateArgs, environmentSettings, templatePackageManager, selectedTemplateGroup).ConfigureAwait(false);
+ }
+
+ protected override InstantiateCommandArgs ParseContext(ParseResult parseResult) => new(this, parseResult);
+
+ private async Task HandleTemplateInstantationAsync(
+ InstantiateCommandArgs args,
+ IEngineEnvironmentSettings environmentSettings,
+ TemplatePackageManager templatePackageManager,
+ TemplateGroup templateGroup)
+ {
+ HashSet candidates = GetTemplateCommand(args, environmentSettings, templatePackageManager, templateGroup);
+ if (candidates.Count == 1)
+ {
+ Command commandToRun = _parentCommand is null ? this : _parentCommand;
+
+ commandToRun.AddCommand(candidates.First());
+ return (NewCommandStatus)await commandToRun.InvokeAsync(args.TokensToInvoke).ConfigureAwait(false);
+ }
+ else if (candidates.Any())
+ {
+ return HandleAmbuguousResult();
+ }
+
+ //TODO: handle it better
+ Reporter.Error.WriteLine(
+ string.Format(LocalizableStrings.NoTemplatesMatchingInputParameters, args.ShortName).Bold().Red());
+ Reporter.Error.WriteLine();
+
+ Reporter.Error.WriteLine(LocalizableStrings.ListTemplatesCommand);
+ Reporter.Error.WriteCommand(CommandExamples.ListCommandExample(args.CommandName));
+
+ Reporter.Error.WriteLine(LocalizableStrings.SearchTemplatesCommand);
+ Reporter.Error.WriteCommand(CommandExamples.SearchCommandExample(args.CommandName, args.ShortName));
+ Reporter.Error.WriteLine();
+ return NewCommandStatus.NotFound;
+ }
+
+ private NewCommandStatus HandleAmbuguousResult() => throw new NotImplementedException();
}
internal class InstantiateCommandArgs : GlobalArgs
{
public InstantiateCommandArgs(InstantiateCommand command, ParseResult parseResult) : base(command, parseResult)
{
- OutputPath = parseResult.GetValueForOption(OutputPathOption);
+ RemainingArguments = parseResult.GetValueForArgument(command.RemainingArguments) ?? Array.Empty();
+ ShortName = parseResult.GetValueForArgument(command.ShortNameArgument);
+ HelpRequested = parseResult.GetValueForOption(command.HelpOption);
+
+ var tokens = new List();
+ if (!string.IsNullOrWhiteSpace(ShortName))
+ {
+ tokens.Add(ShortName);
+ }
+ tokens.AddRange(RemainingArguments);
+ if (HelpRequested)
+ {
+ tokens.Add(command.HelpOption.Aliases.First());
+ }
+ TokensToInvoke = tokens.ToArray();
+
}
- public string? OutputPath { get; }
+ internal string? ShortName { get; }
- private static Option OutputPathOption { get; } = new(new[] { "-o", "--output" })
- {
- Description = "Location to place the generated output. The default is the current directory."
- };
+ internal string[] RemainingArguments { get; }
- internal static void AddToCommand(Command command)
- {
- command.AddOption(OutputPathOption);
- }
+ internal bool HelpRequested { get; }
+ internal string[] TokensToInvoke { get; }
}
}
diff --git a/src/Microsoft.TemplateEngine.Cli/Commands/NewCommand.cs b/src/Microsoft.TemplateEngine.Cli/Commands/NewCommand.cs
index 10971d5ab36..da979e5748f 100644
--- a/src/Microsoft.TemplateEngine.Cli/Commands/NewCommand.cs
+++ b/src/Microsoft.TemplateEngine.Cli/Commands/NewCommand.cs
@@ -5,7 +5,6 @@
using System.CommandLine;
using System.CommandLine.Invocation;
-using System.CommandLine.IO;
using System.CommandLine.Parsing;
using Microsoft.TemplateEngine.Abstractions;
using Microsoft.TemplateEngine.Edge.Settings;
@@ -24,12 +23,8 @@ internal class NewCommand : BaseCommand
FilterOptionDefinition.PackageFilter
};
- private readonly string _commandName;
-
internal NewCommand(string commandName, ITemplateEngineHost host, ITelemetryLogger telemetryLogger, NewCommandCallbacks callbacks) : base(host, telemetryLogger, callbacks, commandName, LocalizableStrings.CommandDescription)
{
- _commandName = commandName;
-
this.AddArgument(ShortNameArgument);
this.AddArgument(RemainingArguments);
this.AddOption(HelpOption);
@@ -65,6 +60,7 @@ internal NewCommand(string commandName, ITemplateEngineHost host, ITelemetryLogg
this.Add(new ListCommand(this, host, telemetryLogger, callbacks));
this.Add(new LegacyListCommand(this, host, telemetryLogger, callbacks));
+
}
internal Argument ShortNameArgument { get; } = new Argument("template-short-name")
@@ -132,90 +128,50 @@ protected override IEnumerable 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 matchingTemplates = templates.Where(template => template.ShortNameList.Contains(args.ShortName));
- HashSet distinctSuggestions = new HashSet();
-
- foreach (var template in matchingTemplates)
- {
- var templateGroupCommand = new TemplateGroupCommand(this, environmentSettings, template);
- var parsed = templateGroupCommand.Parse(args.Arguments ?? Array.Empty());
- foreach (var suggestion in templateGroupCommand.GetSuggestions(parsed, textToMatch))
- {
- if (distinctSuggestions.Add(suggestion))
- {
- yield return suggestion;
- }
- }
- }
- yield break;
- }
- else
- {
- foreach (var template in templates)
- {
- foreach (var suggestion in template.ShortNameList)
- {
- yield return suggestion;
- }
- }
- }
+ return Array.Empty();
- foreach (var suggestion in base.GetSuggestions(args, environmentSettings, textToMatch))
- {
- yield return suggestion;
- }
+ //TODO: implement correct logic
+ //if (!string.IsNullOrEmpty(args.ShortName))
+ //{
+ // var matchingTemplates = templates.Where(template => template.ShortNameList.Contains(args.ShortName));
+ // HashSet distinctSuggestions = new HashSet();
+
+ // foreach (var template in matchingTemplates)
+ // {
+ // var templateGroupCommand = new TemplateGroupCommand(this, environmentSettings, template);
+ // var parsed = templateGroupCommand.Parse(args.Arguments ?? Array.Empty());
+ // foreach (var suggestion in templateGroupCommand.GetSuggestions(parsed, textToMatch))
+ // {
+ // if (distinctSuggestions.Add(suggestion))
+ // {
+ // yield return suggestion;
+ // }
+ // }
+ // }
+ // yield break;
+ //}
+ //else
+ //{
+ // foreach (var template in templates)
+ // {
+ // foreach (var suggestion in template.ShortNameList)
+ // {
+ // yield return suggestion;
+ // }
+ // }
+ //}
+
+ //foreach (var suggestion in base.GetSuggestions(args, environmentSettings, textToMatch))
+ //{
+ // yield return suggestion;
+ //}
}
- protected override async Task ExecuteAsync(NewCommandArgs args, IEngineEnvironmentSettings environmentSettings, InvocationContext context)
+ protected override Task ExecuteAsync(NewCommandArgs args, IEngineEnvironmentSettings environmentSettings, InvocationContext context)
{
- if (string.IsNullOrWhiteSpace(args.ShortName) && args.HelpRequested)
- {
- context.HelpBuilder.Write(
- context.ParseResult.CommandResult.Command,
- StandardStreamWriter.Create(context.Console.Out),
- context.ParseResult);
-
- return NewCommandStatus.Success;
- }
-
- using TemplatePackageManager templatePackageManager = new TemplatePackageManager(environmentSettings);
-
- if (string.IsNullOrWhiteSpace(args.ShortName))
- {
- TemplateListCoordinator templateListCoordinator = new TemplateListCoordinator(
- environmentSettings,
- templatePackageManager,
- new HostSpecificDataLoader(environmentSettings),
- TelemetryLogger);
-
- //TODO: we need to await, otherwise templatePackageManager will be disposed.
- return await templateListCoordinator.DisplayCommandDescriptionAsync(args, default).ConfigureAwait(false);
- }
-
- var templates = await templatePackageManager.GetTemplatesAsync(context.GetCancellationToken()).ConfigureAwait(false);
- var template = templates.FirstOrDefault(template => template.ShortNameList.Contains(args.ShortName));
-
- if (template == null)
- {
- Reporter.Error.WriteLine($"Template {args.ShortName} doesn't exist.");
- return NewCommandStatus.NotFound;
- }
-
- //var dotnet = new Command("dotnet")
- //{
- // TreatUnmatchedTokensAsErrors = false
- //};
- var newC = new Command(_commandName)
- {
- TreatUnmatchedTokensAsErrors = false
- };
- //dotnet.AddCommand(newC);
- newC.AddCommand(new TemplateGroupCommand(this, environmentSettings, template));
-
- return (NewCommandStatus)newC.Invoke(context.ParseResult.Tokens.Select(s => s.Value).ToArray());
+ InstantiateCommand command = InstantiateCommand.FromNewCommand(this);
+ ParseResult reparseResult = TemplateParserFactory.CreateParser(command).Parse(args.Tokens);
+ return command.ExecuteAsync(reparseResult, environmentSettings, context);
}
protected override NewCommandArgs ParseContext(ParseResult parseResult) => new(this, parseResult);
@@ -268,5 +224,6 @@ protected override async Task ExecuteAsync(NewCommandArgs args
}
return null;
}
+
}
}
diff --git a/src/Microsoft.TemplateEngine.Cli/Commands/NewCommandArgs.cs b/src/Microsoft.TemplateEngine.Cli/Commands/NewCommandArgs.cs
index ae19b22a2fb..993ef07d920 100644
--- a/src/Microsoft.TemplateEngine.Cli/Commands/NewCommandArgs.cs
+++ b/src/Microsoft.TemplateEngine.Cli/Commands/NewCommandArgs.cs
@@ -11,15 +11,9 @@ internal class NewCommandArgs : GlobalArgs
{
public NewCommandArgs(NewCommand command, ParseResult parseResult) : base(command, parseResult)
{
- Arguments = parseResult.GetValueForArgument(command.RemainingArguments);
- ShortName = parseResult.GetValueForArgument(command.ShortNameArgument);
- HelpRequested = parseResult.GetValueForOption(command.HelpOption);
+ Tokens = parseResult.Tokens.Select(t => t.Value).ToArray();
}
- internal string? ShortName { get; }
-
- internal string[]? Arguments { get; }
-
- internal bool HelpRequested { get; }
+ internal string[] Tokens { get; }
}
}
diff --git a/src/Microsoft.TemplateEngine.Cli/Commands/TemplateArgs.cs b/src/Microsoft.TemplateEngine.Cli/Commands/TemplateArgs.cs
new file mode 100644
index 00000000000..1d294e88202
--- /dev/null
+++ b/src/Microsoft.TemplateEngine.Cli/Commands/TemplateArgs.cs
@@ -0,0 +1,96 @@
+// 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.Parsing;
+
+namespace Microsoft.TemplateEngine.Cli.Commands
+{
+ internal class TemplateArgs
+ {
+ private readonly ParseResult _parseResult;
+ private readonly TemplateCommand _command;
+ private Dictionary _templateOptions = new Dictionary();
+
+ public TemplateArgs(TemplateCommand command, ParseResult parseResult)
+ {
+ _parseResult = parseResult ?? throw new ArgumentNullException(nameof(parseResult));
+ _command = command ?? throw new ArgumentNullException(nameof(command));
+
+ Name = parseResult.GetValueForOptionOrNull(command.NameOption);
+ OutputPath = parseResult.GetValueForOptionOrNull(command.OutputOption);
+ IsForceFlagSpecified = parseResult.GetValueForOption(command.ForceOption);
+ IsDryRun = parseResult.GetValueForOption(command.DryRunOption);
+ NoUpdateCheck = parseResult.GetValueForOption(command.NoUpdateCheckOption);
+ AllowScripts = parseResult.GetValueForOption(command.AllowScriptsOption);
+
+ if (command.LanguageOption != null)
+ {
+ Language = parseResult.GetValueForOptionOrNull(command.LanguageOption);
+ }
+ if (command.TypeOption != null)
+ {
+ Type = parseResult.GetValueForOptionOrNull(command.TypeOption);
+ }
+ if (command.BaselineOption != null)
+ {
+ BaselineName = parseResult.GetValueForOptionOrNull(command.BaselineOption);
+ }
+
+ foreach (var opt in command.TemplateOptions)
+ {
+ if (parseResult.FindResultFor(opt.Value) is { } result)
+ {
+ _templateOptions[opt.Key] = result;
+ }
+ }
+ Template = command.Template;
+ NewCommandName = parseResult.GetNewCommandName();
+ }
+
+ public string? Name { get; }
+
+ public string? OutputPath { get; }
+
+ public bool IsForceFlagSpecified { get; }
+
+ public string? Language { get; }
+
+ public string? Type { get; }
+
+ public string? BaselineName { get; }
+
+ public bool IsDryRun { get; }
+
+ public bool NoUpdateCheck { get; }
+
+ public AllowRunScripts? AllowScripts { get; }
+
+ public CliTemplateInfo Template { get; }
+
+ public IReadOnlyDictionary TemplateParameters
+ {
+ get
+ {
+ return _templateOptions.Select(o => (o.Key, _parseResult.GetValueForOptionOrNull(o.Value.Option)))
+ .Where(kvp => kvp.Item2 != null)
+ .ToDictionary(kvp => kvp.Key, kvp => kvp.Item2);
+ }
+
+ }
+
+ internal string NewCommandName { get; private set; }
+
+ public bool TryGetAliasForCanonicalName(string canonicalName, out string? alias)
+ {
+ if (_command.TemplateOptions.ContainsKey(canonicalName))
+ {
+ alias = _command.TemplateOptions[canonicalName].Aliases.First();
+ return true;
+ }
+ alias = null;
+ return false;
+ }
+ }
+}
diff --git a/src/Microsoft.TemplateEngine.Cli/Commands/TemplateCommand.cs b/src/Microsoft.TemplateEngine.Cli/Commands/TemplateCommand.cs
new file mode 100644
index 00000000000..758d17e4b13
--- /dev/null
+++ b/src/Microsoft.TemplateEngine.Cli/Commands/TemplateCommand.cs
@@ -0,0 +1,251 @@
+// 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 System.CommandLine.Invocation;
+using Microsoft.TemplateEngine.Abstractions;
+using Microsoft.TemplateEngine.Abstractions.Installer;
+using Microsoft.TemplateEngine.Cli.Extensions;
+using Microsoft.TemplateEngine.Edge.Settings;
+using Microsoft.TemplateEngine.Utils;
+
+namespace Microsoft.TemplateEngine.Cli.Commands
+{
+ internal class TemplateCommand : Command, ICommandHandler
+ {
+ private readonly TemplatePackageManager _templatePackageManager;
+ private readonly IEngineEnvironmentSettings _environmentSettings;
+ private readonly InstantiateCommand _instantiateCommand;
+ private readonly TemplateGroup _templateGroup;
+ private readonly CliTemplateInfo _template;
+ private Dictionary _templateSpecificOptions = new Dictionary();
+
+ public TemplateCommand(
+ InstantiateCommand instantiateCommand,
+ IEngineEnvironmentSettings environmentSettings,
+ TemplatePackageManager templatePackageManager,
+ TemplateGroup templateGroup,
+ CliTemplateInfo template)
+ : base(
+ templateGroup.ShortNames[0],
+ template.Name + Environment.NewLine + template.Description)
+ {
+ _instantiateCommand = instantiateCommand;
+ _environmentSettings = environmentSettings;
+ _templatePackageManager = templatePackageManager;
+ _templateGroup = templateGroup;
+ _template = template;
+ foreach (var item in templateGroup.ShortNames.Skip(1))
+ {
+ AddAlias(item);
+ }
+
+ this.AddOption(OutputOption);
+ this.AddOption(NameOption);
+ this.AddOption(DryRunOption);
+ this.AddOption(ForceOption);
+ this.AddOption(NoUpdateCheckOption);
+ this.AddOption(AllowScriptsOption);
+
+ string? templateLanguage = template.GetLanguage();
+
+ if (!string.IsNullOrWhiteSpace(templateLanguage))
+ {
+ LanguageOption = SharedOptionsFactory.CreateLanguageOption();
+ LanguageOption.FromAmong(templateLanguage);
+ if (templateGroup.Languages.Count > 1)
+ {
+ LanguageOption.SetDefaultValue(environmentSettings.GetDefaultLanguage());
+ LanguageOption.AddValidator(optionResult =>
+ {
+ var value = optionResult.GetValueOrDefault();
+ if (value != template.GetLanguage())
+ {
+ return "Languages don't match";
+ }
+ return null;
+ }
+ );
+ }
+ this.AddOption(LanguageOption);
+ }
+
+ string? templateType = template.GetTemplateType();
+
+ if (!string.IsNullOrWhiteSpace(templateType))
+ {
+ TypeOption = SharedOptionsFactory.CreateTypeOption();
+ TypeOption.FromAmong(templateType);
+ this.AddOption(TypeOption);
+ }
+
+ if (template.BaselineInfo.Any(b => string.IsNullOrWhiteSpace(b.Key)))
+ {
+ BaselineOption = SharedOptionsFactory.CreateBaselineOption();
+ BaselineOption.FromAmong(template.BaselineInfo.Select(b => b.Key).Where(b => !string.IsNullOrWhiteSpace(b)).ToArray());
+ this.AddOption(BaselineOption);
+ }
+
+ AddTemplateOptionsToCommand(template);
+ this.Handler = this;
+ }
+
+ internal Option OutputOption { get; } = new Option(new string[] { "-o", "--output" })
+ {
+ Description = LocalizableStrings.OptionDescriptionOutput,
+ Arity = new ArgumentArity(0, 1)
+ };
+
+ internal Option NameOption { get; } = new Option(new string[] { "-n", "--name" })
+ {
+ Description = LocalizableStrings.OptionDescriptionName,
+ Arity = new ArgumentArity(0, 1)
+ };
+
+ internal Option DryRunOption { get; } = new Option("--dry-run")
+ {
+ Description = LocalizableStrings.OptionDescriptionDryRun,
+ Arity = new ArgumentArity(0, 1)
+ };
+
+ internal Option ForceOption { get; } = new Option("--force")
+ {
+ Description = LocalizableStrings.OptionDescriptionForce,
+ Arity = new ArgumentArity(0, 1)
+ };
+
+ internal Option NoUpdateCheckOption { get; } = new Option("--no-update-check")
+ {
+ Description = LocalizableStrings.OptionDescriptionNoUpdateCheck,
+ Arity = new ArgumentArity(0, 1)
+ };
+
+ internal Option AllowScriptsOption { get; } = new Option("--allow-scripts")
+ {
+ Description = LocalizableStrings.OptionDescriptionAllowScripts,
+ IsHidden = true,
+ Arity = new ArgumentArity(0, 1)
+ };
+
+ internal Option? LanguageOption { get; }
+
+ internal Option? TypeOption { get; }
+
+ internal Option? BaselineOption { get; }
+
+ internal IReadOnlyDictionary TemplateOptions => _templateSpecificOptions;
+
+ internal CliTemplateInfo Template => _template;
+
+ public async Task InvokeAsync(InvocationContext context)
+ {
+ TemplateArgs args = new TemplateArgs(this, context.ParseResult);
+
+ TemplateInvoker invoker = new TemplateInvoker(_environmentSettings, _instantiateCommand.TelemetryLogger, () => Console.ReadLine() ?? string.Empty, _instantiateCommand.Callbacks);
+ if (!args.NoUpdateCheck)
+ {
+ TemplatePackageCoordinator packageCoordinator = new TemplatePackageCoordinator(_instantiateCommand.TelemetryLogger, _environmentSettings, _templatePackageManager);
+ Task checkForUpdateTask = packageCoordinator.CheckUpdateForTemplate(args.Template, context.GetCancellationToken());
+ Task instantiateTask = invoker.InvokeTemplateAsync(args, context.GetCancellationToken());
+ await Task.WhenAll(checkForUpdateTask, instantiateTask).ConfigureAwait(false);
+
+ if (checkForUpdateTask?.Result != null)
+ {
+ // print if there is update for this template
+ packageCoordinator.DisplayUpdateCheckResult(checkForUpdateTask.Result, args.NewCommandName);
+ }
+ // return creation result
+ return (int)instantiateTask.Result;
+ }
+ else
+ {
+ return (int)await invoker.InvokeTemplateAsync(args, context.GetCancellationToken()).ConfigureAwait(false);
+ }
+ }
+
+ private static ArgumentArity GetOptionArity(CliTemplateParameter parameter) => new ArgumentArity(parameter.IsRequired ? 1 : 0, 1);
+
+ private HashSet GetReservedAliases()
+ {
+ HashSet reservedAliases = new HashSet();
+ foreach (string alias in this.Children.OfType