From 3ee21bf44b1b4689b9910e3036cd0b0285520a66 Mon Sep 17 00:00:00 2001 From: TheCakeIsNaOH 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 | 7 +- .../services/TemplateService.cs | 65 ++++++++++++++++++- .../infrastructure/tokens/TokenReplacer.cs | 19 ++++-- 3 files changed, 83 insertions(+), 8 deletions(-) diff --git a/src/chocolatey.tests/infrastructure.app/services/TemplateServiceSpecs.cs b/src/chocolatey.tests/infrastructure.app/services/TemplateServiceSpecs.cs index 7f9f2802be..b9e42f493f 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 fileSystem = new Mock(); + protected Mock XmlService = new Mock(); public override void Context() { - fileSystem.ResetCalls(); + 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 349c9b251a..172fec22ed 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 _templateBinaryExtensions = new List { ".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,53 @@ protected void list_built_in_template_info(ChocolateyConfiguration configuration } } } + + protected IEnumerable 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>(cacheFilePath); + } + // If the template is not installed via a package, always read the parameters directly as the template may have been updated manually + else + { + return get_template_parameters_from_files(configuration).ToList(); + } + } + + protected HashSet 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(); + + 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 14f37fa96f..9bbc3b628f 100644 --- a/src/chocolatey/infrastructure/tokens/TokenReplacer.cs +++ b/src/chocolatey/infrastructure/tokens/TokenReplacer.cs @@ -1,13 +1,13 @@ // Copyright © 2017 - 2021 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. -// +// // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -61,5 +61,16 @@ private static IDictionary create_dictionary_from_configuration< return propertyDictionary; } + + public static IEnumerable get_tokens(string textWithTokens, string tokenPrefix = "[[", string tokenSuffix = "]]") + { + var regexMatches = Regex.Matches(textWithTokens, "{0}(?\\w+){1}" + .format_with(Regex.Escape(tokenPrefix), Regex.Escape(tokenSuffix)) + ); + foreach (Match regexMatch in regexMatches) + { + yield return regexMatch.Groups["key"].to_string(); + } + } } } \ No newline at end of file