From ca9cfc32e80a49abb4b3ff253fb757ada50ab96d Mon Sep 17 00:00:00 2001 From: TheCakeIsNaOH Date: Fri, 10 Dec 2021 22:46:53 -0600 Subject: [PATCH] (#449) Add template command This allows a user to get information about all installed templates, or a specific template. The list subcommand lists all installed templates. The info subcommand lists verbose information about a specific template Usage with -r should output a machine parsable list of the installed templates. Usage with verbose (implied with the info subcommand) outputs more information about the template(s). The information displayed here can be gathered by running the list command, config command and inspecting the contents of the TemplatesLocation. However, this command allows the information to be displayed in one location. --- src/chocolatey/chocolatey.csproj | 2 + .../commands/ChocolateyTemplateCommand.cs | 157 ++++++++++++++++ .../configuration/ChocolateyConfiguration.cs | 16 ++ .../domain/TemplateCommandType.cs | 25 +++ .../registration/ContainerBinding.cs | 3 +- .../services/ITemplateService.cs | 2 + .../services/TemplateService.cs | 167 +++++++++++++++++- 7 files changed, 370 insertions(+), 2 deletions(-) create mode 100644 src/chocolatey/infrastructure.app/commands/ChocolateyTemplateCommand.cs create mode 100644 src/chocolatey/infrastructure.app/domain/TemplateCommandType.cs diff --git a/src/chocolatey/chocolatey.csproj b/src/chocolatey/chocolatey.csproj index eea25ebe2d..1301daa138 100644 --- a/src/chocolatey/chocolatey.csproj +++ b/src/chocolatey/chocolatey.csproj @@ -113,6 +113,8 @@ Properties\SolutionVersion.cs + + diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyTemplateCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyTemplateCommand.cs new file mode 100644 index 0000000000..110c5bf10b --- /dev/null +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyTemplateCommand.cs @@ -0,0 +1,157 @@ +// 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. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.commands +{ + using System; + using System.Collections.Generic; + using System.Linq; + using attributes; + using commandline; + using configuration; + using domain; + using infrastructure.commands; + using logging; + using services; + using templates; + + [CommandFor("template", "get information about installed templates")] + [CommandFor("templates", "get information about installed templates (alias for template)")] + public class ChocolateyTemplateCommand : ICommand + { + private readonly ITemplateService _templateService; + + public ChocolateyTemplateCommand(ITemplateService templateService) + { + _templateService = templateService; + } + + public void configure_argument_parser(OptionSet optionSet, ChocolateyConfiguration configuration) + { + optionSet + .Add("n=|name=", + "The name of the template to get information about.", + option => configuration.TemplateCommand.Name = option.remove_surrounding_quotes().ToLower()); + // TODO Allow for templates from external path? If PR 1477 is merged + } + + public virtual void handle_additional_argument_parsing(IList unparsedArguments, ChocolateyConfiguration configuration) + { + // don't set configuration.Input or it will be passed to list + + if (unparsedArguments.Count > 1) + { + throw new ApplicationException("A single template command must be listed. Please see the help menu for those commands"); + } + + var command = TemplateCommandType.unknown; + string unparsedCommand = unparsedArguments.DefaultIfEmpty(string.Empty).FirstOrDefault(); + Enum.TryParse(unparsedCommand, true, out command); + + if (command == TemplateCommandType.unknown) + { + if (!string.IsNullOrWhiteSpace(unparsedCommand)) this.Log().Warn("Unknown command {0}. Setting to list.".format_with(unparsedCommand)); + command = TemplateCommandType.list; + } + + configuration.TemplateCommand.Command = command; + } + + public virtual void handle_validation(ChocolateyConfiguration configuration) + { + if (configuration.TemplateCommand.Command != TemplateCommandType.list && string.IsNullOrWhiteSpace(configuration.TemplateCommand.Name)) + { + throw new ApplicationException("When specifying the subcommand '{0}', you must also specify --name.".format_with(configuration.TemplateCommand.Command.to_string())); + } + } + + public virtual void help_message(ChocolateyConfiguration configuration) + { + "chocolatey".Log().Info(ChocolateyLoggers.Important, "Template Command"); + "chocolatey".Log().Info(@" +List information installed templates. + +Both manually installed templates and templates installed via + .template packages are displayed."); + + "chocolatey".Log().Info(ChocolateyLoggers.Important, "Usage"); + "chocolatey".Log().Info(@" + choco pin [list]|info []"); + + "chocolatey".Log().Info(ChocolateyLoggers.Important, "Examples"); + "chocolatey".Log().Info(@" + choco template + choco templates + choco template list + choco template info --name msi + choco template list --reduce-output + choco template list --verbose + +NOTE: See scripting in the command reference (`choco -?`) for how to + write proper scripts and integrations. + +"); + + "chocolatey".Log().Info(ChocolateyLoggers.Important, "Exit Codes"); + "chocolatey".Log().Info(@" +Exit codes that normally result from running this command. + +Normal: + - 0: operation was successful, no issues detected + - -1 or 1: an error has occurred + +If you find other exit codes that we have not yet documented, please + file a ticket so we can document it at + https://github.com/chocolatey/choco/issues/new/choose. + +"); + + "chocolatey".Log().Info(ChocolateyLoggers.Important, "Options and Switches"); + } + + public virtual void noop(ChocolateyConfiguration configuration) + { + switch (configuration.TemplateCommand.Command) + { + case TemplateCommandType.list: + _templateService.list_noop(configuration); + break; + case TemplateCommandType.info: + _templateService.list_noop(configuration); + break; + } + } + + public virtual void run(ChocolateyConfiguration configuration) + { + switch (configuration.TemplateCommand.Command) + { + case TemplateCommandType.list: + _templateService.list(configuration); + break; + case TemplateCommandType.info: + configuration.Verbose = true; + _templateService.list(configuration); + break; + } + } + + public virtual bool may_require_admin_access() + { + return false; + } + } +} \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs index c4ec782330..f597e7b1fe 100644 --- a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs +++ b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs @@ -51,6 +51,7 @@ public ChocolateyConfiguration() OutdatedCommand = new OutdatedCommandConfiguration(); Proxy = new ProxyConfiguration(); ExportCommand = new ExportCommandConfiguration(); + TemplateCommand = new TemplateCommandConfiguration(); #if DEBUG AllowUnofficialBuild = true; #endif @@ -345,6 +346,14 @@ private void append_output(StringBuilder propertyValues, string append) /// On .NET 4.0, get error CS0200 when private set - see http://stackoverflow.com/a/23809226/18475 /// public ProxyConfiguration Proxy { get; set; } + + /// + /// Configuration related specifically to Template command + /// + /// + /// On .NET 4.0, get error CS0200 when private set - see http://stackoverflow.com/a/23809226/18475 + /// + public TemplateCommandConfiguration TemplateCommand { get; set; } } [Serializable] @@ -557,4 +566,11 @@ public sealed class ExportCommandConfiguration public string OutputFilePath { get; set; } } + + [Serializable] + public sealed class TemplateCommandConfiguration + { + public TemplateCommandType Command { get; set; } + public string Name { get; set; } + } } \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/domain/TemplateCommandType.cs b/src/chocolatey/infrastructure.app/domain/TemplateCommandType.cs new file mode 100644 index 0000000000..8f4cb5bcc1 --- /dev/null +++ b/src/chocolatey/infrastructure.app/domain/TemplateCommandType.cs @@ -0,0 +1,25 @@ +// 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. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.domain +{ + public enum TemplateCommandType + { + unknown, + list, + info + } +} diff --git a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs index a849a379ee..8e94690ee9 100644 --- a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs +++ b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs @@ -97,7 +97,8 @@ public void RegisterComponents(Container container) new ChocolateyUnpackSelfCommand(container.GetInstance()), new ChocolateyVersionCommand(container.GetInstance()), new ChocolateyUpdateCommand(container.GetInstance()), - new ChocolateyExportCommand(container.GetInstance(), container.GetInstance()) + new ChocolateyExportCommand(container.GetInstance(), container.GetInstance()), + new ChocolateyTemplateCommand(container.GetInstance()) }; return list.AsReadOnly(); }, Lifestyle.Singleton); diff --git a/src/chocolatey/infrastructure.app/services/ITemplateService.cs b/src/chocolatey/infrastructure.app/services/ITemplateService.cs index abfe00dfa0..ec757dd50d 100644 --- a/src/chocolatey/infrastructure.app/services/ITemplateService.cs +++ b/src/chocolatey/infrastructure.app/services/ITemplateService.cs @@ -22,5 +22,7 @@ public interface ITemplateService { void generate_noop(ChocolateyConfiguration configuration); void generate(ChocolateyConfiguration configuration); + void list_noop(ChocolateyConfiguration configuration); + void list(ChocolateyConfiguration configuration); } } diff --git a/src/chocolatey/infrastructure.app/services/TemplateService.cs b/src/chocolatey/infrastructure.app/services/TemplateService.cs index 7a68b491fc..12e986ff2e 100644 --- a/src/chocolatey/infrastructure.app/services/TemplateService.cs +++ b/src/chocolatey/infrastructure.app/services/TemplateService.cs @@ -19,18 +19,23 @@ namespace chocolatey.infrastructure.app.services using System; using System.Collections.Generic; using System.IO; + using System.Linq; using System.Reflection; using System.Text; using configuration; - using filesystem; using logging; using templates; using tokens; + using NuGet; + using nuget; + using IFileSystem = filesystem.IFileSystem; public class TemplateService : ITemplateService { private readonly UTF8Encoding utf8WithoutBOM = new UTF8Encoding(false); private readonly IFileSystem _fileSystem; + private readonly ILogger _nugetLogger; + private readonly IList _templateBinaryExtensions = new List { ".exe", ".msi", ".msu", ".msp", ".mst", ".7z", ".zip", ".rar", ".gz", ".iso", ".tar", ".sfx", @@ -38,6 +43,9 @@ public class TemplateService : ITemplateService ".cer", ".crt", ".der", ".p7b", ".pfx", ".p12", ".pem" }; + private readonly string _builtInTemplateOverrideName = "default"; + private readonly string _builtInTemplateName = "built-in"; + public TemplateService(IFileSystem fileSystem) { _fileSystem = fileSystem; @@ -186,5 +194,162 @@ public void generate_file_from_template(ChocolateyConfiguration configuration, T _fileSystem.create_directory_if_not_exists(_fileSystem.get_directory_name(fileLocation)); _fileSystem.write_file(fileLocation, template, encoding); } + + public void list_noop(ChocolateyConfiguration configuration) + { + if (string.IsNullOrWhiteSpace(configuration.TemplateCommand.Name)) + { + this.Log().Info(() => "Would have listed templates in {0}\\templates".format_with(ApplicationParameters.InstallLocation)); + } + else + { + this.Log().Info(() => "Would have listed information about {0}".format_with(configuration.TemplateCommand.Name)); + } + } + + public void list(ChocolateyConfiguration configuration) + { + + var packageManager = NugetCommon.GetPackageManager(configuration, _nugetLogger, + new PackageDownloader(), + installSuccessAction: null, + uninstallSuccessAction: null, + addUninstallHandler: false); + + var templateDirList = _fileSystem.get_directories(ApplicationParameters.TemplatesLocation).ToList(); + var isBuiltInTemplateOverriden = templateDirList.Contains(_fileSystem.combine_paths(ApplicationParameters.TemplatesLocation, _builtInTemplateOverrideName)); + var isBuiltInOrDefaultTemplateDefault = string.IsNullOrWhiteSpace(configuration.DefaultTemplateName) || !templateDirList.Contains(_fileSystem.combine_paths(ApplicationParameters.TemplatesLocation, configuration.DefaultTemplateName)); + + if (string.IsNullOrWhiteSpace(configuration.TemplateCommand.Name)) + { + + if (templateDirList.Any()) + { + foreach (var templateDir in templateDirList) + { + configuration.TemplateCommand.Name = _fileSystem.get_file_name(templateDir); + list_custom_template_info(configuration, packageManager); + } + + this.Log().Info(configuration.RegularOutput ? "{0} Custom templates found at {1}{2}".format_with(templateDirList.Count(), ApplicationParameters.TemplatesLocation, Environment.NewLine) : string.Empty); + } + else + { + this.Log().Info(configuration.RegularOutput ? "No custom templates installed in {0}{2}".format_with(ApplicationParameters.TemplatesLocation, Environment.NewLine) : string.Empty); + } + + list_built_in_template_info(configuration, isBuiltInTemplateOverriden, isBuiltInOrDefaultTemplateDefault); + } + else + { + if (templateDirList.Contains(_fileSystem.combine_paths(ApplicationParameters.TemplatesLocation, configuration.TemplateCommand.Name))) + { + list_custom_template_info(configuration, packageManager); + if (configuration.TemplateCommand.Name == _builtInTemplateName || configuration.TemplateCommand.Name == _builtInTemplateOverrideName) + { + list_built_in_template_info(configuration, isBuiltInTemplateOverriden, isBuiltInOrDefaultTemplateDefault); + } + } + else + { + if (configuration.TemplateCommand.Name.ToLowerInvariant() == _builtInTemplateName || configuration.TemplateCommand.Name.ToLowerInvariant() == _builtInTemplateOverrideName) + { + // We know that the template is not overriden since the template directory was checked + list_built_in_template_info(configuration, isBuiltInTemplateOverriden, isBuiltInOrDefaultTemplateDefault); + } + else + { + throw new ApplicationException("Unable to find requested template '{0}'".format_with(configuration.TemplateCommand.Name)); + } + } + } + } + + protected void list_custom_template_info(ChocolateyConfiguration configuration, IPackageManager packageManager) + { + + var pkg = packageManager.LocalRepository.FindPackage("{0}.template".format_with(configuration.TemplateCommand.Name)); + var pkgVersion = (pkg == null) ? "0.0.0" : pkg.Version.to_string(); + var pkgTitle = (pkg == null) ? "{0} (Unmanaged)".format_with(configuration.TemplateCommand.Name) : pkg.Title; + var pkgSummary = (pkg == null) ? + string.Empty : (pkg.Summary != null && !string.IsNullOrWhiteSpace(pkg.Summary.to_string()) ? "{0}".format_with(pkg.Summary.escape_curly_braces().to_string()) : string.Empty); + var pkgDescription = (pkg == null) ? string.Empty : pkg.Description.escape_curly_braces().Replace("\n ", "\n").Replace("\n", "\n "); + var pkgFiles = " {0}".format_with(string.Join("{0} " + .format_with(Environment.NewLine), _fileSystem.get_files(_fileSystem + .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); + + if (configuration.RegularOutput) + { + if (configuration.Verbose) + { + // TODO list parameters, do later (save to file?) + this.Log().Info(@"Template name: {0} +Version: {1} +Default template: {2} +{3}Title: {4} +{5}{6} +List of files: +{7} +List of Parameters: + Will be implemented at a later time +".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)); + } + else + { + this.Log().Info("{0} {1} {2}".format_with((isDefault ? '*' : ' '), configuration.TemplateCommand.Name, pkgVersion)); + } + } + else + { + this.Log().Info("{0}|{1}".format_with(configuration.TemplateCommand.Name, pkgVersion)); + } + } + + protected void list_built_in_template_info(ChocolateyConfiguration configuration, bool isOverridden, bool isDefault) + { + if (configuration.RegularOutput) + { + if (isOverridden) + { + this.Log().Info("Built-in template overriden by 'default' template.{0}".format_with(Environment.NewLine)); + } + else + { + if (isDefault) + { + this.Log().Info("Built-in template is default.{0}".format_with(Environment.NewLine)); + } + else + { + this.Log().Info("Built-in template is not default, it can be specified if the --built-in parameter is used{0}".format_with(Environment.NewLine)); + } + + } + + if (configuration.Verbose) + { + this.Log().Info("Help about the built-in template can be found with 'choco new --help'{0}".format_with(Environment.NewLine)); + } + } + else + { + //If reduced output, only print out the built in template if it is not overriden + if (!isOverridden) + { + this.Log().Info("built-in|0.0.0"); + } + } + + } } }