Skip to content

Commit

Permalink
(chocolatey#2500) List parameters in templates
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
TheCakeIsNaOH committed Aug 11, 2022
1 parent 974a3bc commit 2b73662
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}
}

Expand Down
65 changes: 63 additions & 2 deletions src/chocolatey/infrastructure.app/services/TemplateService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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",
Expand All @@ -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)
Expand Down Expand Up @@ -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));
Expand All @@ -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);
Expand Down Expand Up @@ -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)
{
Expand All @@ -292,14 +302,17 @@ 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,
isOverridingBuiltIn ? "This template is overriding the built in template{0}".format_with(Environment.NewLine) : string.Empty,
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
{
Expand Down Expand Up @@ -345,5 +358,53 @@ 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
else
{
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;
}
}
}
11 changes: 11 additions & 0 deletions src/chocolatey/infrastructure/tokens/TokenReplacer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
}
}

0 comments on commit 2b73662

Please sign in to comment.