From 565c083abf3954880fa203fee43220b9189f3bfa Mon Sep 17 00:00:00 2001 From: TheCakeIsNaOH <TheCakeIsNaOH@gmail.com> Date: Mon, 27 Dec 2021 23:28:26 -0600 Subject: [PATCH] (#2500) List parameters in templates This adds the ability for the template command to list the parameters in templates. If the template is installed via a package, the parameters get cached in a file called .parameters in the template's directory. This file is ignored by calls to choco new, as it is excluded from being copied. There another method added to TokenReplacer to get the parameters via regex. This keeps the regex in the TokenReplacer class. --- .../services/TemplateServiceSpecs.cs | 5 +- .../services/TemplateService.cs | 63 ++++++++++++++++++- .../infrastructure/tokens/TokenReplacer.cs | 11 ++++ 3 files changed, 76 insertions(+), 3 deletions(-) diff --git a/src/chocolatey.tests/infrastructure.app/services/TemplateServiceSpecs.cs b/src/chocolatey.tests/infrastructure.app/services/TemplateServiceSpecs.cs index 48b3f2ef71..82db549daa 100644 --- a/src/chocolatey.tests/infrastructure.app/services/TemplateServiceSpecs.cs +++ b/src/chocolatey.tests/infrastructure.app/services/TemplateServiceSpecs.cs @@ -26,6 +26,7 @@ namespace chocolatey.tests.infrastructure.app.services using chocolatey.infrastructure.app.services; using chocolatey.infrastructure.app.templates; using chocolatey.infrastructure.filesystem; + using chocolatey.infrastructure.services; using Moq; using NUnit.Framework; using Should; @@ -36,12 +37,14 @@ public abstract class TemplateServiceSpecsBase : TinySpec { protected TemplateService service; protected Mock<IFileSystem> fileSystem = new Mock<IFileSystem>(); + protected Mock<IXmlService> xmlService = new Mock<IXmlService>(); public override void Context() { fileSystem.ResetCalls(); + xmlService.ResetCalls(); - service = new TemplateService(fileSystem.Object); + service = new TemplateService(fileSystem.Object, xmlService.Object); } } diff --git a/src/chocolatey/infrastructure.app/services/TemplateService.cs b/src/chocolatey/infrastructure.app/services/TemplateService.cs index 224b287b30..9e6d2355d2 100644 --- a/src/chocolatey/infrastructure.app/services/TemplateService.cs +++ b/src/chocolatey/infrastructure.app/services/TemplateService.cs @@ -23,6 +23,7 @@ namespace chocolatey.infrastructure.app.services using System.Reflection; using System.Text; using configuration; + using infrastructure.services; using logging; using templates; using tokens; @@ -35,6 +36,7 @@ public class TemplateService : ITemplateService private readonly UTF8Encoding utf8WithoutBOM = new UTF8Encoding(false); private readonly IFileSystem _fileSystem; private readonly ILogger _nugetLogger; + private readonly IXmlService _xmlService; private readonly IList<string> _templateBinaryExtensions = new List<string> { ".exe", ".msi", ".msu", ".msp", ".mst", @@ -45,10 +47,12 @@ public class TemplateService : ITemplateService private readonly string _builtInTemplateOverrideName = "default"; private readonly string _builtInTemplateName = "built-in"; + private readonly string _templateParameterCacheFilename = ".parameters"; - public TemplateService(IFileSystem fileSystem) + public TemplateService(IFileSystem fileSystem, IXmlService xmlService) { _fileSystem = fileSystem; + _xmlService = xmlService; } public void generate_noop(ChocolateyConfiguration configuration) @@ -148,6 +152,7 @@ public void generate(ChocolateyConfiguration configuration) configuration.NewCommand.TemplateName = string.IsNullOrWhiteSpace(configuration.NewCommand.TemplateName) ? "default" : configuration.NewCommand.TemplateName; var templatePath = _fileSystem.combine_paths(ApplicationParameters.TemplatesLocation, configuration.NewCommand.TemplateName); + var templateParameterCachePath = _fileSystem.combine_paths(templatePath, _templateParameterCacheFilename); if (!_fileSystem.directory_exists(templatePath)) throw new ApplicationException("Unable to find path to requested template '{0}'. Path should be '{1}'".format_with(configuration.NewCommand.TemplateName, templatePath)); this.Log().Info(configuration.QuietOutput ? logger : ChocolateyLoggers.Important, "Generating package from custom template at '{0}'.".format_with(templatePath)); @@ -174,6 +179,10 @@ public void generate(ChocolateyConfiguration configuration) this.Log().Debug(" Treating template file ('{0}') as binary instead of replacing templated values.".format_with(_fileSystem.get_file_name(file))); _fileSystem.copy_file(file, packageFileLocation, overwriteExisting:true); } + else if (templateParameterCachePath.is_equal_to(file)) + { + this.Log().Debug("{0} is the parameter cache file, ignoring".format_with(file)); + } else { generate_file_from_template(configuration, tokens, _fileSystem.read_file(file), packageFileLocation, Encoding.UTF8); @@ -280,6 +289,7 @@ protected void list_custom_template_info(ChocolateyConfiguration configuration, .combine_paths(ApplicationParameters.TemplatesLocation, configuration.TemplateCommand.Name), "*", SearchOption.AllDirectories))); var isOverridingBuiltIn = configuration.TemplateCommand.Name == _builtInTemplateOverrideName; var isDefault = string.IsNullOrWhiteSpace(configuration.DefaultTemplateName) ? isOverridingBuiltIn : (configuration.DefaultTemplateName == configuration.TemplateCommand.Name); + var templateParams = " {0}".format_with(string.Join("{0} ".format_with(Environment.NewLine), get_template_parameters(configuration, templateInstalledViaPackage))); if (configuration.RegularOutput) { @@ -292,6 +302,8 @@ protected void list_custom_template_info(ChocolateyConfiguration configuration, {5}{6} List of files: {7} +List of Parameters: +{8} ".format_with(configuration.TemplateCommand.Name, pkgVersion, isDefault, @@ -299,7 +311,8 @@ protected void list_custom_template_info(ChocolateyConfiguration configuration, pkgTitle, string.IsNullOrEmpty(pkgSummary) ? "Template not installed as a package" : "Summary: {0}".format_with(pkgSummary), string.IsNullOrEmpty(pkgDescription) ? string.Empty : "{0}Description:{0} {1}".format_with(Environment.NewLine, pkgDescription), - pkgFiles)); + pkgFiles, + templateParams)); } else { @@ -345,5 +358,51 @@ protected void list_built_in_template_info(ChocolateyConfiguration configuration } } } + + protected IEnumerable<string> get_template_parameters(ChocolateyConfiguration configuration, bool templateInstalledViaPackage) + { + // If the template was installed via package, the cache file gets removed on upgrade, so the cache file would be up to date if it exists + if (templateInstalledViaPackage) + { + var templateDirectory = _fileSystem.combine_paths(ApplicationParameters.TemplatesLocation, configuration.TemplateCommand.Name); + var cacheFilePath = _fileSystem.combine_paths(templateDirectory, _templateParameterCacheFilename); + + if (!_fileSystem.file_exists(cacheFilePath)) + { + _xmlService.serialize(get_template_parameters_from_files(configuration).ToList(), cacheFilePath); + } + + return _xmlService.deserialize<List<string>>(cacheFilePath); + } + // If the template is not installed via a package, always read the parameters directly as the template may have been updated manually + + return get_template_parameters_from_files(configuration).ToList(); + } + + protected HashSet<string> get_template_parameters_from_files(ChocolateyConfiguration configuration) + { + var filesList = _fileSystem.get_files(_fileSystem.combine_paths(ApplicationParameters.TemplatesLocation, configuration.TemplateCommand.Name), "*", SearchOption.AllDirectories); + var parametersList = new HashSet<string>(); + + foreach (var filePath in filesList) + { + if (_templateBinaryExtensions.Contains(_fileSystem.get_file_extension(filePath))) + { + this.Log().Debug("{0} is a binary file, not reading parameters".format_with(filePath)); + continue; + } + + if (_fileSystem.get_file_name(filePath) == _templateParameterCacheFilename) + { + this.Log().Debug("{0} is the parameter cache file, not reading parameters".format_with(filePath)); + continue; + } + + var fileContents = _fileSystem.read_file(filePath); + parametersList.UnionWith(TokenReplacer.get_tokens(fileContents, "[[", "]]")); + } + + return parametersList; + } } } diff --git a/src/chocolatey/infrastructure/tokens/TokenReplacer.cs b/src/chocolatey/infrastructure/tokens/TokenReplacer.cs index 50b7541abb..8c1e4028d0 100644 --- a/src/chocolatey/infrastructure/tokens/TokenReplacer.cs +++ b/src/chocolatey/infrastructure/tokens/TokenReplacer.cs @@ -61,5 +61,16 @@ private static IDictionary<string, string> create_dictionary_from_configuration< return propertyDictionary; } + + public static IEnumerable<string> get_tokens(string textWithTokens, string tokenPrefix = "[[", string tokenSuffix = "]]") + { + var regexMatches = Regex.Matches(textWithTokens, "{0}(?<key>\\w+){1}" + .format_with(Regex.Escape(tokenPrefix), Regex.Escape(tokenSuffix)) + ); + foreach (Match regexMatch in regexMatches) + { + yield return regexMatch.Groups["key"].to_string(); + } + } } }