From 6b0461132bf13ba5d4bab8561c815c70364938c1 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Fri, 15 May 2015 09:00:52 -0500 Subject: [PATCH] (GH-170) Add outdated command This adds the outdated command, similar to `brew outdated`. --- src/chocolatey.tests/chocolatey.tests.csproj | 1 + .../ChocolateyOutdatedCommandSpecs.cs | 142 ++++++++++++++++++ src/chocolatey/chocolatey.csproj | 1 + .../commands/ChocolateyOutdatedCommand.cs | 102 +++++++++++++ .../configuration/ChocolateyConfiguration.cs | 1 + .../domain/CommandNameType.cs | 1 + .../registration/ContainerBinding.cs | 1 + .../services/ChocolateyPackageService.cs | 42 ++++++ .../services/IChocolateyPackageService.cs | 12 ++ .../services/NugetService.cs | 35 +++-- 10 files changed, 324 insertions(+), 14 deletions(-) create mode 100644 src/chocolatey.tests/infrastructure.app/commands/ChocolateyOutdatedCommandSpecs.cs create mode 100644 src/chocolatey/infrastructure.app/commands/ChocolateyOutdatedCommand.cs diff --git a/src/chocolatey.tests/chocolatey.tests.csproj b/src/chocolatey.tests/chocolatey.tests.csproj index 0afc2017fd..9033d98df5 100644 --- a/src/chocolatey.tests/chocolatey.tests.csproj +++ b/src/chocolatey.tests/chocolatey.tests.csproj @@ -68,6 +68,7 @@ + diff --git a/src/chocolatey.tests/infrastructure.app/commands/ChocolateyOutdatedCommandSpecs.cs b/src/chocolatey.tests/infrastructure.app/commands/ChocolateyOutdatedCommandSpecs.cs new file mode 100644 index 0000000000..4a67f2dad1 --- /dev/null +++ b/src/chocolatey.tests/infrastructure.app/commands/ChocolateyOutdatedCommandSpecs.cs @@ -0,0 +1,142 @@ +// Copyright © 2011 - Present 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.tests.infrastructure.app.commands +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Moq; + using Should; + using chocolatey.infrastructure.app.attributes; + using chocolatey.infrastructure.app.commands; + using chocolatey.infrastructure.app.configuration; + using chocolatey.infrastructure.app.domain; + using chocolatey.infrastructure.app.services; + using chocolatey.infrastructure.commandline; + + public class ChocolateyOutdatedCommandSpecs + { + public abstract class ChocolateyOutdatedCommandSpecsBase : TinySpec + { + protected ChocolateyOutdatedCommand command; + protected Mock packageService = new Mock(); + protected ChocolateyConfiguration configuration = new ChocolateyConfiguration(); + + public override void Context() + { + configuration.Sources = "bob"; + command = new ChocolateyOutdatedCommand(packageService.Object); + } + } + + public class when_implementing_command_for : ChocolateyOutdatedCommandSpecsBase + { + private List results; + + public override void Because() + { + results = command.GetType().GetCustomAttributes(typeof (CommandForAttribute), false).Cast().Select(a => a.CommandName).ToList(); + } + + [Fact] + public void should_implement_outdated() + { + results.ShouldContain(CommandNameType.outdated.to_string()); + } + } + + public class when_configurating_the_argument_parser : ChocolateyOutdatedCommandSpecsBase + { + private OptionSet optionSet; + + public override void Context() + { + base.Context(); + optionSet = new OptionSet(); + } + + public override void Because() + { + command.configure_argument_parser(optionSet, configuration); + } + + [Fact] + public void should_add_source_to_the_option_set() + { + optionSet.Contains("source").ShouldBeTrue(); + } + + [Fact] + public void should_add_short_version_of_source_to_the_option_set() + { + optionSet.Contains("s").ShouldBeTrue(); + } + + [Fact] + public void should_add_user_to_the_option_set() + { + optionSet.Contains("user").ShouldBeTrue(); + } + + [Fact] + public void should_add_short_version_of_user_to_the_option_set() + { + optionSet.Contains("u").ShouldBeTrue(); + } + + [Fact] + public void should_add_password_to_the_option_set() + { + optionSet.Contains("password").ShouldBeTrue(); + } + + [Fact] + public void should_add_short_version_of_password_to_the_option_set() + { + optionSet.Contains("p").ShouldBeTrue(); + } + + } + + public class when_noop_is_called : ChocolateyOutdatedCommandSpecsBase + { + public override void Because() + { + command.noop(configuration); + } + + [Fact] + public void should_call_service_outdated_noop() + { + packageService.Verify(c => c.outdated_noop(configuration), Times.Once); + } + } + + public class when_run_is_called : ChocolateyOutdatedCommandSpecsBase + { + public override void Because() + { + command.run(configuration); + } + + [Fact] + public void should_call_service_oudated_run() + { + packageService.Verify(c => c.outdated_run(configuration), Times.Once); + } + } + } +} \ No newline at end of file diff --git a/src/chocolatey/chocolatey.csproj b/src/chocolatey/chocolatey.csproj index f75bdca93a..2daec58295 100644 --- a/src/chocolatey/chocolatey.csproj +++ b/src/chocolatey/chocolatey.csproj @@ -81,6 +81,7 @@ + diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyOutdatedCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyOutdatedCommand.cs new file mode 100644 index 0000000000..7a608c6959 --- /dev/null +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyOutdatedCommand.cs @@ -0,0 +1,102 @@ +// Copyright © 2011 - Present 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.Collections.Generic; + using attributes; + using commandline; + using configuration; + using domain; + using infrastructure.commands; + using logging; + using services; + + [CommandFor(CommandNameType.outdated)] + public sealed class ChocolateyOutdatedCommand : ICommand + { + private readonly IChocolateyPackageService _packageService; + + public ChocolateyOutdatedCommand(IChocolateyPackageService packageService) + { + _packageService = packageService; + } + + public void configure_argument_parser(OptionSet optionSet, ChocolateyConfiguration configuration) + { + optionSet + .Add("s=|source=", + "Source - The source to find the package(s) to install. Special sources include: ruby, webpi, cygwin, windowsfeatures, and python. Defaults to default feeds.", + option => configuration.Sources = option.remove_surrounding_quotes()) + .Add("u=|user=", + "User - used with authenticated feeds. Defaults to empty.", + option => configuration.SourceCommand.Username = option.remove_surrounding_quotes()) + .Add("p=|password=", + "Password - the user's password to the source. Defaults to empty.", + option => configuration.SourceCommand.Password = option.remove_surrounding_quotes()) + ; + } + + public void handle_additional_argument_parsing(IList unparsedArguments, ChocolateyConfiguration configuration) + { + configuration.Input = string.Join(" ", unparsedArguments); + configuration.PackageNames = string.Join(ApplicationParameters.PackageNamesSeparator.to_string(), unparsedArguments); + } + + public void handle_validation(ChocolateyConfiguration configuration) + { + } + + public void help_message(ChocolateyConfiguration configuration) + { + this.Log().Info(ChocolateyLoggers.Important, "Outdated Command"); + this.Log().Info(@" +Returns a list of outdated packages +"); + + "chocolatey".Log().Info(ChocolateyLoggers.Important, "Usage"); + "chocolatey".Log().Info(@" + choco outdated [] +"); + + "chocolatey".Log().Info(ChocolateyLoggers.Important, "Examples"); + "chocolatey".Log().Info(@" + choco outdated + choco outdated -s ""https://somewhere/out/there"" + choco outdated -s ""https://somewhere/protected"" -u user -p pass + +If you use `--source=https://somewhere/out/there`, it is + going to look for outdated packages only based on that source. +"); + + "chocolatey".Log().Info(ChocolateyLoggers.Important, "Options and Switches"); + } + + public void noop(ChocolateyConfiguration configuration) + { + _packageService.outdated_noop(configuration); + } + + public void run(ChocolateyConfiguration configuration) + { + _packageService.outdated_run(configuration); + } + + public 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 dadd8b4322..7b6abd6984 100644 --- a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs +++ b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs @@ -313,6 +313,7 @@ public sealed class ListCommandConfiguration public sealed class UpgradeCommandConfiguration { public bool FailOnUnfound { get; set; } + public bool NotifyOnlyAvailableUpgrades { get; set; } } [Serializable] diff --git a/src/chocolatey/infrastructure.app/domain/CommandNameType.cs b/src/chocolatey/infrastructure.app/domain/CommandNameType.cs index 334ad2f4c3..d2e54a032f 100644 --- a/src/chocolatey/infrastructure.app/domain/CommandNameType.cs +++ b/src/chocolatey/infrastructure.app/domain/CommandNameType.cs @@ -27,6 +27,7 @@ public enum CommandNameType //[Description("update - updates package index")] update, [Description("update - [DEPRECATED] RESERVED for future use (you are looking for upgrade, these are not the droids you are looking for)")] update, [Description("upgrade - upgrades packages from various sources")] upgrade, + [Description("outdated - retrieves packages that are outdated. Similar to upgrade all --noop")] outdated, [Description("uninstall - uninstalls a package")] uninstall, [Description("source - view and configure default sources")] source, [Description("sources - view and configure default sources (alias for source)")] diff --git a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs index 794b3463b5..fd35fb716a 100644 --- a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs +++ b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs @@ -72,6 +72,7 @@ public void RegisterComponents(Container container) new ChocolateyListCommand(container.GetInstance()), new ChocolateyInstallCommand(container.GetInstance()), new ChocolateyPinCommand(container.GetInstance(), container.GetInstance(), container.GetInstance()), + new ChocolateyOutdatedCommand(container.GetInstance()), new ChocolateyUpgradeCommand(container.GetInstance()), new ChocolateyUninstallCommand(container.GetInstance()), new ChocolateyPackCommand(container.GetInstance()), diff --git a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs index aa8db5f3ba..2cddc469d6 100644 --- a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs +++ b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs @@ -284,6 +284,48 @@ public ConcurrentDictionary install_run(ChocolateyConfigu return packageInstalls; } + public void outdated_noop(ChocolateyConfiguration config) + { + this.Log().Info(@" +Would have determined packages that are out of date based on what is + installed and what versions are available for upgrade."); + } + + public void outdated_run(ChocolateyConfiguration config) + { + this.Log().Info(ChocolateyLoggers.Important, @"Outdated Packages + Output is package name | current version | available version | pinned? +"); + + config.PackageNames = ApplicationParameters.AllPackages; + config.UpgradeCommand.NotifyOnlyAvailableUpgrades = true; + + var output = config.RegularOutput; + config.RegularOutput = false; + var oudatedPackages = _nugetService.upgrade_noop(config, null); + config.RegularOutput = output; + + if (config.RegularOutput) + { + var upgradeWarnings = oudatedPackages.Count(p => p.Value.Warning); + this.Log().Warn(() => @"{0}{1} has determined {2} package(s) are outdated. {3}.".format_with( + Environment.NewLine, + ApplicationParameters.Name, + oudatedPackages.Count(p => p.Value.Success && !p.Value.Inconclusive), + upgradeWarnings == 0 ? string.Empty : "{0} {1} package(s) had warnings.".format_with(Environment.NewLine, upgradeWarnings) + )); + + if (upgradeWarnings != 0) + { + this.Log().Warn(ChocolateyLoggers.Important, "Warnings:"); + foreach (var warning in oudatedPackages.Where(p => p.Value.Warning).or_empty_list_if_null()) + { + this.Log().Warn(ChocolateyLoggers.Important, " - {0}".format_with(warning.Value.Name)); + } + } + } + } + private IEnumerable set_config_from_package_names_and_packages_config(ChocolateyConfiguration config, ConcurrentDictionary packageInstalls) { // if there are any .config files, split those off of the config. Then return the config without those package names. diff --git a/src/chocolatey/infrastructure.app/services/IChocolateyPackageService.cs b/src/chocolatey/infrastructure.app/services/IChocolateyPackageService.cs index 7e468977a5..45b0e2f206 100644 --- a/src/chocolatey/infrastructure.app/services/IChocolateyPackageService.cs +++ b/src/chocolatey/infrastructure.app/services/IChocolateyPackageService.cs @@ -75,6 +75,18 @@ public interface IChocolateyPackageService /// results of installs ConcurrentDictionary install_run(ChocolateyConfiguration config); + /// + /// Run outdated in noop mode + /// + /// The configuration. + void outdated_noop(ChocolateyConfiguration config); + + /// + /// Determines all packages that are out of date + /// + /// The configuration. + void outdated_run(ChocolateyConfiguration config); + /// /// Run upgrade in noop mode /// diff --git a/src/chocolatey/infrastructure.app/services/NugetService.cs b/src/chocolatey/infrastructure.app/services/NugetService.cs index b9359f10b2..1c2ccc30f5 100644 --- a/src/chocolatey/infrastructure.app/services/NugetService.cs +++ b/src/chocolatey/infrastructure.app/services/NugetService.cs @@ -505,14 +505,18 @@ packages as of version 1.0.0. That is what the install command is for. string logMessage = "{0} v{1} is newer than the most recent.{2} You must be smarter than the average bear...".format_with(installedPackage.Id, installedPackage.Version, Environment.NewLine); packageResult.Messages.Add(new ResultMessage(ResultType.Inconclusive, logMessage)); - if (config.RegularOutput) - { - this.Log().Info(ChocolateyLoggers.Important, logMessage); - } - else + if (!config.UpgradeCommand.NotifyOnlyAvailableUpgrades) { - this.Log().Info("{0}|{1}|{1}|{2}".format_with(installedPackage.Id, installedPackage.Version, isPinned.to_string().to_lower())); + if (config.RegularOutput) + { + this.Log().Info(ChocolateyLoggers.Important, logMessage); + } + else + { + this.Log().Info("{0}|{1}|{1}|{2}".format_with(installedPackage.Id, installedPackage.Version, isPinned.to_string().to_lower())); + } } + continue; } @@ -527,15 +531,18 @@ packages as of version 1.0.0. That is what the install command is for. packageResult.Messages.Add(new ResultMessage(ResultType.Inconclusive, logMessage)); } - if (config.RegularOutput) - { - this.Log().Info(logMessage); - } - else + if (!config.UpgradeCommand.NotifyOnlyAvailableUpgrades) { - this.Log().Info("{0}|{1}|{2}|{3}".format_with(installedPackage.Id, installedPackage.Version, availablePackage.Version, isPinned.to_string().to_lower())); + if (config.RegularOutput) + { + this.Log().Info(logMessage); + } + else + { + this.Log().Info("{0}|{1}|{2}|{3}".format_with(installedPackage.Id, installedPackage.Version, availablePackage.Version, isPinned.to_string().to_lower())); + } } - + continue; } @@ -547,7 +554,7 @@ packages as of version 1.0.0. That is what the install command is for. { if (availablePackage.Version > installedPackage.Version) { - string logMessage = "You have {0} v{1} installed. Version {2} is available based on your source(s)".format_with(installedPackage.Id, installedPackage.Version, availablePackage.Version); + string logMessage = "You have {0} v{1} installed. Version {2} is available based on your source(s).".format_with(installedPackage.Id, installedPackage.Version, availablePackage.Version); packageResult.Messages.Add(new ResultMessage(ResultType.Note, logMessage)); if (config.RegularOutput)