File: RunnableProjectGenerator.cs
Web Access
Project: src\sdk\src\TemplateEngine\Microsoft.TemplateEngine.Orchestrator.RunnableProjects\Microsoft.TemplateEngine.Orchestrator.RunnableProjects.csproj (Microsoft.TemplateEngine.Orchestrator.RunnableProjects)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Extensions.Logging;
using Microsoft.TemplateEngine.Abstractions;
using Microsoft.TemplateEngine.Abstractions.Constraints;
using Microsoft.TemplateEngine.Abstractions.Mount;
using Microsoft.TemplateEngine.Abstractions.Parameters;
using Microsoft.TemplateEngine.Core;
using Microsoft.TemplateEngine.Core.Contracts;
using Microsoft.TemplateEngine.Core.Expressions.Cpp2;
using Microsoft.TemplateEngine.Orchestrator.RunnableProjects.Abstractions;
using Microsoft.TemplateEngine.Orchestrator.RunnableProjects.Validation;
using Microsoft.TemplateEngine.Utils;

namespace Microsoft.TemplateEngine.Orchestrator.RunnableProjects
{
    public sealed class RunnableProjectGenerator : IGenerator
    {
        internal const string TemplateConfigDirectoryName = ".template.config";
        internal const string TemplateConfigFileName = "template.json";

        internal const string GeneratorVersion = "1.0.0.0";
        private static readonly Guid GeneratorId = new("0C434DF7-E2CB-4DEE-B216-D7C58C8EB4B3");

        /// <inheritdoc/>
        Guid IIdentifiedComponent.Id => GeneratorId;

        /// <inheritdoc/>
        async Task<IReadOnlyList<IScanTemplateInfo>> IGenerator.GetTemplatesFromMountPointAsync(IMountPoint source, CancellationToken cancellationToken)
        {
            return await GetTemplatesFromMountPointInternalAsync(source, cancellationToken).ConfigureAwait(false);
        }

        /// <inheritdoc/>
        async Task<ITemplate?> IGenerator.LoadTemplateAsync(IEngineEnvironmentSettings settings, ITemplateLocator templateLocator, string? baselineName, CancellationToken cancellationToken)
        {
            IMountPoint? mountPoint = null;
            IFile? configFile = null;
            try
            {
                if (!settings.TryGetMountPoint(templateLocator.MountPointUri, out mountPoint))
                {
                    return null;
                }
                if (mountPoint == null)
                {
                    throw new InvalidOperationException($"{nameof(mountPoint)} is null after {nameof(EngineEnvironmentSettingsExtensions.TryGetMountPoint)} returned true.");
                }
                configFile = mountPoint.FileInfo(templateLocator.ConfigPlace);
                if (configFile == null)
                {
                    return null;
                }
                IFile? localeConfig = null;
                IFile? hostTemplateConfigFile = null;
                if (templateLocator is IExtendedTemplateLocator extendedTemplateLocator)
                {
                    localeConfig = string.IsNullOrWhiteSpace(extendedTemplateLocator.LocaleConfigPlace) ? null : mountPoint.FileInfo(extendedTemplateLocator.LocaleConfigPlace!);
                    hostTemplateConfigFile = string.IsNullOrWhiteSpace(extendedTemplateLocator.HostConfigPlace) ? null : mountPoint.FileInfo(extendedTemplateLocator.HostConfigPlace!);
                }

                RunnableProjectConfig loadedTemplate = new RunnableProjectConfig(settings, this, configFile, hostTemplateConfigFile, localeConfig, baselineName);
                await loadedTemplate.ValidateAsync(ValidationScope.Instantiation, cancellationToken).ConfigureAwait(false);
                if (loadedTemplate.Localization != null && loadedTemplate.Localization.IsValid)
                {
                    loadedTemplate.Localize();
                }

                return loadedTemplate;
            }
            catch (InvalidOperationException)
            {
                throw;
            }
            catch (NotSupportedException ex)
            {
                //do not print stack trace for this type.
                settings.Host.Logger.LogError(LocalizableStrings.Authoring_TemplateNotInstalled_Message, configFile?.GetDisplayPath(), ex.Message);
            }
            catch (TemplateAuthoringException ex)
            {
                //do not print stack trace for this type.
                settings.Host.Logger.LogError(LocalizableStrings.Authoring_TemplateNotInstalled_Message, configFile?.GetDisplayPath(), ex.Message);
            }
            catch (Exception ex)
            {
                //unexpected error - print details
                settings.Host.Logger.LogError(LocalizableStrings.Authoring_TemplateNotInstalled_Message, configFile?.GetDisplayPath(), ex);
            }
            return null;
        }

        /// <summary>
        /// Converts the raw, string version of a parameter to a strongly typed value. If the parameter has a datatype specified, use that. Otherwise attempt to infer the type.
        /// Throws a TemplateParamException if the conversion fails for any reason.
        /// </summary>
        object? IGenerator.ConvertParameterValueToType(IEngineEnvironmentSettings environmentSettings, ITemplateParameter parameter, string untypedValue, out bool valueResolutionError)
        {
            valueResolutionError = !ParameterConverter.TryConvertParameterValueToType(parameter, untypedValue, out object? convertedValue);
            return convertedValue;
        }

        /// <inheritdoc/>
        bool IGenerator.TryEvaluateFromString(ILogger logger, string text, IDictionary<string, object> variables, out bool result, out string evaluationError, HashSet<string>? referencedVariablesKeys)
        {
            VariableCollection variableCollection = new(null, variables);
            result = Cpp2StyleEvaluatorDefinition.EvaluateFromString(logger, text, variableCollection, out evaluationError!, referencedVariablesKeys);
            return string.IsNullOrEmpty(evaluationError);
        }

        /// <inheritdoc/>
        async Task<ICreationEffects> IGenerator.GetCreationEffectsAsync(
            IEngineEnvironmentSettings environmentSettings,
            ITemplate templateData,
            IParameterSetData parameters,
            string targetDirectory,
            CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            if (templateData is not IRunnableProjectConfig templateConfig)
            {
                throw new InvalidOperationException($"Load template using {nameof(IGenerator.LoadTemplateAsync)} to use this method.");
            }
            if (templateData.TemplateSourceRoot is null)
            {
                throw new InvalidOperationException($"{nameof(templateData.TemplateSourceRoot)} cannot be null to continue.");
            }

            RemoveDisabledParameters(parameters, templateConfig);
            IVariableCollection variables = SetupVariables(parameters, templateConfig.GlobalOperationConfig.VariableSetup);
            await templateConfig.EvaluateBindSymbolsAsync(environmentSettings, variables, cancellationToken).ConfigureAwait(false);
            cancellationToken.ThrowIfCancellationRequested();
            IReadOnlyList<IMacroConfig> sortedMacroConfigs = MacroProcessor.SortMacroConfigsByDependencies(templateConfig.GlobalOperationConfig.SymbolNames, templateConfig.GlobalOperationConfig.Macros);
            MacroProcessor.ProcessMacros(environmentSettings, sortedMacroConfigs, variables);
            templateConfig.Evaluate(variables);

            IOrchestrator basicOrchestrator = new Core.Util.Orchestrator(environmentSettings.Host.Logger, environmentSettings.Host.FileSystem);
            RunnableProjectOrchestrator orchestrator = new(basicOrchestrator);

            GlobalRunSpec runSpec = new GlobalRunSpec(templateData.TemplateSourceRoot, environmentSettings.Components, variables, templateConfig);
            List<IFileChange2> changes = new List<IFileChange2>();

            foreach (FileSourceMatchInfo source in templateConfig.EvaluatedSources)
            {
                runSpec.SetupFileSource(source);
                string target = Path.Combine(targetDirectory, source.Target);
                IDirectory? sourceDirectory =
                    templateData.TemplateSourceRoot.DirectoryInfo(source.Source) ??
                    throw new InvalidOperationException($"Cannot access the source directory of the template: {source.Source}.");
                IReadOnlyList<IFileChange2> fileChanges = orchestrator.GetFileChanges(runSpec, sourceDirectory, target);

                //source and target paths in the file changes are returned relative to source passed
                //GetCreationEffects method should return the source paths relative to template source root (location of .template.config folder) and target paths relative to output path and not relative to certain source
                //add source and target used to file changes to be returned as the result
                changes.AddRange(
                    fileChanges.Select(
                        fileChange => new FileChange(
                            Path.Combine(source.Source, fileChange.SourceRelativePath),
                            Path.Combine(source.Target, fileChange.TargetRelativePath),
                            fileChange.ChangeKind,
#pragma warning disable CS0618 // Type or member is obsolete
                            fileChange.Contents)));
#pragma warning restore CS0618 // Type or member is obsolete
            }

            return new CreationEffects2(changes, GetCreationResult(templateConfig));
        }

        /// <inheritdoc/>
        Task<ICreationResult> IGenerator.CreateAsync(
            IEngineEnvironmentSettings environmentSettings,
            ITemplate templateData,
            IParameterSetData parameters,
            string targetDirectory,
            CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            if (templateData is not RunnableProjectConfig templateConfig)
            {
                throw new InvalidOperationException($"Load template using {nameof(IGenerator.LoadTemplateAsync)} to use this method.");
            }
            if (templateData.TemplateSourceRoot is null)
            {
                throw new InvalidOperationException($"{nameof(templateData.TemplateSourceRoot)} cannot be null to continue.");
            }

            return CreateAsync(
                environmentSettings,
                templateConfig,
                templateData.TemplateSourceRoot,
                parameters,
                targetDirectory,
                cancellationToken);
        }

        #region Obsolete members

        [Obsolete("Replaced by CreateAsync with IEvaluatedParameterSetData", false)]
        Task<ICreationResult> IGenerator.CreateAsync(
            IEngineEnvironmentSettings environmentSettings,
            ITemplate template,
            IParameterSet parameters,
            string targetDirectory,
            CancellationToken cancellationToken)
        {
            return ((IGenerator)this).CreateAsync(environmentSettings, template, parameters.ToParameterSetData(), targetDirectory, cancellationToken);
        }

        [Obsolete("Replaced by GetCreationEffectsAsync with IEvaluatedParameterSetData", false)]
        Task<ICreationEffects> IGenerator.GetCreationEffectsAsync(
            IEngineEnvironmentSettings environmentSettings,
            ITemplate template,
            IParameterSet parameters,
            string targetDirectory,
            CancellationToken cancellationToken)
        {
            return ((IGenerator)this).GetCreationEffectsAsync(environmentSettings, template, parameters.ToParameterSetData(), targetDirectory, cancellationToken);
        }

        [Obsolete("Replaced by ParameterSetBuilder.CreateWithDefaults", true)]
        IParameterSet IGenerator.GetParametersForTemplate(IEngineEnvironmentSettings environmentSettings, ITemplate template)
        {
            throw new NotImplementedException("Replaced by ParameterSetBuilder.CreateWithDefaults");
        }

        /// <summary>
        /// Scans the <paramref name="source"/> for the available templates.
        /// </summary>
        /// <param name="source">the mount point to scan for the templates.</param>
        /// <param name="localizations">found localization definitions.</param>
        /// <returns>the list of found templates and list of found localizations via <paramref name="localizations"/>.</returns>
        [Obsolete]
        IList<ITemplate> IGenerator.GetTemplatesAndLangpacksFromDir(IMountPoint source, out IList<ILocalizationLocator> localizations)
        {
            IReadOnlyList<ScannedTemplateInfo> foundTemplates =
                      Task.Run(async () => await GetTemplatesFromMountPointInternalAsync(source, default).ConfigureAwait(false)).GetAwaiter().GetResult();

            localizations = foundTemplates.SelectMany(t => t.Localizations.Values).OfType<ILocalizationLocator>().ToList();

            return foundTemplates.Select(t => (ITemplate)new LegacyTemplate(t, this)).ToList();
        }

        /// <summary>
        /// Attempts to load template configuration from <paramref name="templateFileConfig"/>.
        /// </summary>
        /// <param name="templateFileConfig">the template configuration entry, should be a file.</param>
        /// <param name="template">loaded template.</param>
        /// <param name="localeFileConfig">the template localization configuration entry, should be a file.</param>
        /// <param name="hostTemplateConfigFile">the template host configuration entry, should be a file.</param>
        /// <param name="baselineName">the baseline to load.</param>
        /// <returns>true when template can be loaded, false otherwise. The loaded template is returned in <paramref name="template"/>.</returns>
        [Obsolete]
        bool IGenerator.TryGetTemplateFromConfigInfo(IFileSystemInfo templateFileConfig, out ITemplate? template, IFileSystemInfo? localeFileConfig, IFile? hostTemplateConfigFile, string? baselineName)
        {
            _ = templateFileConfig ?? throw new ArgumentNullException(nameof(templateFileConfig));
            ILogger logger = templateFileConfig.MountPoint.EnvironmentSettings.Host.LoggerFactory.CreateLogger<RunnableProjectGenerator>();
            try
            {
                IFile templateFile = templateFileConfig as IFile
                    ?? throw new NotSupportedException(string.Format(LocalizableStrings.RunnableProjectGenerator_Exception_ConfigShouldBeFile, templateFileConfig.GetDisplayPath()));

                IFile? localeFile = null;
                if (localeFileConfig != null)
                {
                    localeFile = localeFileConfig as IFile
                      ?? throw new NotSupportedException(string.Format(LocalizableStrings.RunnableProjectGenerator_Exception_LocaleConfigShouldBeFile, localeFileConfig.GetDisplayPath()));
                }

                RunnableProjectConfig loadedTemplate = new RunnableProjectConfig(templateFileConfig.MountPoint.EnvironmentSettings, this, templateFile, hostTemplateConfigFile, localeFile, baselineName);
                Task.Run(async () => await loadedTemplate.ValidateAsync(ValidationScope.Instantiation, CancellationToken.None).ConfigureAwait(false)).GetAwaiter().GetResult();
                template = loadedTemplate;
                return true;
            }
            catch (NotSupportedException ex)
            {
                //do not print stack trace for this type.
                logger.LogError(LocalizableStrings.Authoring_TemplateNotInstalled_Message, templateFileConfig.GetDisplayPath(), ex.Message);
            }
            catch (TemplateAuthoringException ex)
            {
                //do not print stack trace for this type.
                logger.LogError(LocalizableStrings.Authoring_TemplateNotInstalled_Message, templateFileConfig.GetDisplayPath(), ex.Message);
            }
            catch (Exception ex)
            {
                //unexpected error - print details
                logger.LogError(LocalizableStrings.Authoring_TemplateNotInstalled_Message, templateFileConfig.GetDisplayPath(), ex);
            }
            template = null;
            return false;
        }

        #endregion

        internal static async Task<ICreationResult> CreateAsync(
            IEngineEnvironmentSettings environmentSettings,
            IRunnableProjectConfig runnableProjectConfig,
            IDirectory templateSourceRoot,
            IParameterSetData parameters,
            string targetDirectory,
            CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();

            RemoveDisabledParameters(parameters, runnableProjectConfig);
            IVariableCollection variables = SetupVariables(parameters, runnableProjectConfig.GlobalOperationConfig.VariableSetup);
            await runnableProjectConfig.EvaluateBindSymbolsAsync(environmentSettings, variables, cancellationToken).ConfigureAwait(false);

            cancellationToken.ThrowIfCancellationRequested();
            IReadOnlyList<IMacroConfig> sortedMacroConfigs = MacroProcessor.SortMacroConfigsByDependencies(runnableProjectConfig.GlobalOperationConfig.SymbolNames, runnableProjectConfig.GlobalOperationConfig.Macros);
            MacroProcessor.ProcessMacros(environmentSettings, sortedMacroConfigs, variables);
            runnableProjectConfig.Evaluate(variables);

            IOrchestrator basicOrchestrator = new Core.Util.Orchestrator(environmentSettings.Host.Logger, environmentSettings.Host.FileSystem);
            RunnableProjectOrchestrator orchestrator = new(basicOrchestrator);

            GlobalRunSpec runSpec = new GlobalRunSpec(templateSourceRoot, environmentSettings.Components, variables, runnableProjectConfig);

            foreach (FileSourceMatchInfo source in runnableProjectConfig.EvaluatedSources)
            {
                runSpec.SetupFileSource(source);
                string target = Path.Combine(targetDirectory, source.Target);
                IDirectory? sourceDirectory =
                    templateSourceRoot.DirectoryInfo(source.Source) ??
                    throw new InvalidOperationException($"Cannot access the source directory of the template: {source.Source}.");
                orchestrator.Run(runSpec, sourceDirectory, target);
            }

            return GetCreationResult(runnableProjectConfig);
        }

        internal async Task<IReadOnlyList<ScannedTemplateInfo>> GetTemplatesFromMountPointInternalAsync(IMountPoint source, CancellationToken cancellationToken)
        {
            _ = source ?? throw new ArgumentNullException(nameof(source));
            ILogger logger = source.EnvironmentSettings.Host.LoggerFactory.CreateLogger<RunnableProjectGenerator>();
            IDirectory folder = source.Root;
            List<ScannedTemplateInfo> templateList = new();

            foreach (IFile file in folder.EnumerateFiles(TemplateConfigFileName, SearchOption.AllDirectories))
            {
                cancellationToken.ThrowIfCancellationRequested();
                logger.LogDebug($"Found {TemplateConfigFileName} at {file.GetDisplayPath()}.");
                try
                {
                    var discoveredTemplate = new ScannedTemplateInfo(source.EnvironmentSettings, this, file);
                    await discoveredTemplate.ValidateAsync(ValidationScope.Scanning, cancellationToken).ConfigureAwait(false);
                    cancellationToken.ThrowIfCancellationRequested();
                    templateList.Add(discoveredTemplate);
                }
                catch (NotSupportedException ex)
                {
                    //do not print stack trace for this type.
                    logger.LogError(LocalizableStrings.Authoring_TemplateNotInstalled_Message, file.GetDisplayPath(), ex.Message);
                }
                catch (TemplateAuthoringException ex)
                {
                    //do not print stack trace for this type.
                    logger.LogError(LocalizableStrings.Authoring_TemplateNotInstalled_Message, file.GetDisplayPath(), ex.Message);
                }
                catch (Exception ex)
                {
                    //unexpected error - print details
                    logger.LogError(LocalizableStrings.Authoring_TemplateNotInstalled_Message, file.GetDisplayPath(), ex);
                }
            }
            return templateList;
        }

        private static void RemoveDisabledParameters(
            IParameterSetData parameters,
            IRunnableProjectConfig runnableProjectConfig)
        {
            parameters.Values
                .Where(v => !v.IsEnabled)
                .ForEach(p => runnableProjectConfig.RemoveParameter(p.ParameterDefinition));
        }

        private static IVariableCollection SetupVariables(IParameterSetData parameters, IVariableConfig variableConfig)
        {
            IVariableCollection variables = ParameterBasedVariableCollection.SetupParameterBasedVariables(parameters, variableConfig);

            foreach (Parameter param in parameters.ParametersDefinition.OfType<Parameter>())
            {
                // Add choice values to variables - to allow them to be recognizable unquoted
                if (param.EnableQuotelessLiterals && param.IsChoice() && param.Choices != null)
                {
                    foreach (string choiceKey in param.Choices.Keys)
                    {
                        if (
                            variables.TryGetValue(choiceKey, out object? existingValueObj) &&
                            existingValueObj is string existingValue &&
                            !string.Equals(choiceKey, existingValue, StringComparison.CurrentCulture))
                        {
                            throw new InvalidOperationException(string.Format(LocalizableStrings.RunnableProjectGenerator_CannotAddImplicitChoice, choiceKey, existingValue));
                        }
                        variables[choiceKey] = choiceKey;
                    }
                }
            }

            return variables;
        }

        private static ICreationResult GetCreationResult(IRunnableProjectConfig runnableProjectConfig)
        {
            return new CreationResult(runnableProjectConfig.PostActions, runnableProjectConfig.PrimaryOutputs);
        }

        /// <summary>
        /// Wrapper class: ScannedTemplateInfo -> ITemplate.
        /// </summary>
        [Obsolete]
        private class LegacyTemplate : ITemplate
        {
            private readonly ScannedTemplateInfo _templateInfo;

            internal LegacyTemplate(ScannedTemplateInfo templateInfo, IGenerator generator)
            {
                _templateInfo = templateInfo;
                Generator = generator;
            }

            public IGenerator Generator { get; }

            public IFileSystemInfo Configuration => _templateInfo.ConfigFile;

            public IFileSystemInfo? LocaleConfiguration => null;

            public IFileSystemInfo? HostSpecificConfiguration => null;

            public IDirectory TemplateSourceRoot => _templateInfo.TemplateSourceRoot;

            public bool IsNameAgreementWithFolderPreferred => _templateInfo.ConfigurationModel.PreferNameDirectory;

            public bool PreferDefaultName => _templateInfo.ConfigurationModel.PreferDefaultName;

            public string? Author => _templateInfo.ConfigurationModel.Author;

            public string? Description => _templateInfo.ConfigurationModel.Description;

            public IReadOnlyList<string> Classifications => _templateInfo.ConfigurationModel.Classifications;

            public string? DefaultName => _templateInfo.ConfigurationModel.DefaultName;

            public string Identity => _templateInfo.ConfigurationModel.Identity;

            public Guid GeneratorId => Generator.Id;

            public string? GroupIdentity => _templateInfo.ConfigurationModel.GroupIdentity;

            public int Precedence => _templateInfo.ConfigurationModel.Precedence;

            public string Name => _templateInfo.ConfigurationModel.Name ?? throw new TemplateAuthoringException("Template configuration should have 'name' defined.", "name");

            public IReadOnlyDictionary<string, string> TagsCollection => _templateInfo.ConfigurationModel.Tags;

            public IParameterDefinitionSet ParameterDefinitions => new ParameterDefinitionSet(_templateInfo.ConfigurationModel.ExtractParameters());

            public IReadOnlyList<ITemplateParameter> Parameters => ParameterDefinitions;

            public string MountPointUri => _templateInfo.ConfigFile.MountPoint.MountPointUri;

            public string ConfigPlace => _templateInfo.ConfigFile.FullPath;

            public string LocaleConfigPlace => throw new NotImplementedException();

            public string HostConfigPlace => throw new NotImplementedException();

            public string? ThirdPartyNotices => _templateInfo.ConfigurationModel.ThirdPartyNotices;

            public IReadOnlyDictionary<string, IBaselineInfo> BaselineInfo => _templateInfo.ConfigurationModel.BaselineInfo;

            public IReadOnlyList<string> ShortNameList => _templateInfo.ConfigurationModel.ShortNameList;

            public IReadOnlyList<Guid> PostActions => _templateInfo.ConfigurationModel.PostActionModels.Select(pam => pam.ActionId).ToArray();

            public IReadOnlyList<TemplateConstraintInfo> Constraints => _templateInfo.ConfigurationModel.Constraints;

            public bool IsValid => !ValidationErrors.Any(e => e.Severity == IValidationEntry.SeverityLevel.Error);

            public IReadOnlyList<IValidationEntry> ValidationErrors => _templateInfo.ValidationErrors;

            public ILocalizationLocator Localization => throw new NotImplementedException();

            #region Obsolete members

            [Obsolete]
            string ITemplateInfo.ShortName
            {
                get
                {
                    if (((ITemplateInfo)this).ShortNameList.Count > 0)
                    {
                        return ((ITemplateInfo)this).ShortNameList[0];
                    }

                    return string.Empty;
                }
            }

            [Obsolete]
            IReadOnlyDictionary<string, ICacheTag> ITemplateInfo.Tags
            {
                get
                {
                    Dictionary<string, ICacheTag> tags = new Dictionary<string, ICacheTag>();
                    foreach (KeyValuePair<string, string> tag in ((ITemplateInfo)this).TagsCollection)
                    {
                        tags[tag.Key] = new CacheTag(null, null, new Dictionary<string, ParameterChoice> { { tag.Value, new ParameterChoice(null, null) } }, tag.Value);
                    }
                    foreach (ITemplateParameter parameter in ((ITemplateInfo)this).ParameterDefinitions.Where(TemplateParameterExtensions.IsChoice))
                    {
                        IReadOnlyDictionary<string, ParameterChoice> choices = parameter.Choices ?? new Dictionary<string, ParameterChoice>();
                        tags[parameter.Name] = new CacheTag(parameter.DisplayName, parameter.Description, choices, parameter.DefaultValue);
                    }
                    return tags;
                }
            }

            [Obsolete]
            IReadOnlyDictionary<string, ICacheParameter> ITemplateInfo.CacheParameters
            {
                get
                {
                    Dictionary<string, ICacheParameter> cacheParameters = new Dictionary<string, ICacheParameter>();
                    foreach (ITemplateParameter parameter in ((ITemplateInfo)this).ParameterDefinitions.Where(TemplateParameterExtensions.IsChoice))
                    {
                        cacheParameters[parameter.Name] = new CacheParameter()
                        {
                            DataType = parameter.DataType,
                            DefaultValue = parameter.DefaultValue,
                            Description = parameter.Documentation,
                            DefaultIfOptionWithoutValue = parameter.DefaultIfOptionWithoutValue,
                            DisplayName = parameter.DisplayName

                        };
                    }
                    return cacheParameters;
                }
            }

            [Obsolete("Use ParameterDefinitionSet instead.")]
            IReadOnlyList<ITemplateParameter> ITemplateInfo.Parameters => ParameterDefinitions;

            [Obsolete]
            bool ITemplateInfo.HasScriptRunningPostActions { get; set; }

            #endregion

            public void Dispose()
            {
            }
        }
    }
}