diff --git a/src/chocolatey/chocolatey.csproj b/src/chocolatey/chocolatey.csproj
index 52069d46ad..1b113a04fb 100644
--- a/src/chocolatey/chocolatey.csproj
+++ b/src/chocolatey/chocolatey.csproj
@@ -111,6 +111,7 @@
+
diff --git a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs
index a5cdb31b06..e1acad8400 100644
--- a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs
+++ b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs
@@ -96,6 +96,7 @@ public void RegisterComponents(Container container)
new WebPiService(container.GetInstance(), container.GetInstance()),
new WindowsFeatureService(container.GetInstance(), container.GetInstance(), container.GetInstance()),
new CygwinService(container.GetInstance(), container.GetInstance(), container.GetInstance(), container.GetInstance()),
+ new PythonService(container.GetInstance(), container.GetInstance(), container.GetInstance(), container.GetInstance()),
new RubyGemsService(container.GetInstance(), container.GetInstance())
};
return list.AsReadOnly();
diff --git a/src/chocolatey/infrastructure.app/services/PythonService.cs b/src/chocolatey/infrastructure.app/services/PythonService.cs
new file mode 100644
index 0000000000..963ec5c39a
--- /dev/null
+++ b/src/chocolatey/infrastructure.app/services/PythonService.cs
@@ -0,0 +1,531 @@
+namespace chocolatey.infrastructure.app.services
+{
+ using System;
+ using System.Collections.Concurrent;
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Text.RegularExpressions;
+ using Microsoft.Win32;
+ using configuration;
+ using domain;
+ using filesystem;
+ using infrastructure.commands;
+ using logging;
+ using results;
+
+ ///
+ /// Alternative Source for Installing Python packages
+ ///
+ public sealed class PythonService : ISourceRunner
+ {
+ private readonly ICommandExecutor _commandExecutor;
+ private readonly INugetService _nugetService;
+ private readonly IFileSystem _fileSystem;
+ private readonly IRegistryService _registryService;
+ private const string PACKAGE_NAME_TOKEN = "{{packagename}}";
+ private const string LOG_LEVEL_TOKEN = "{{loglevel}}";
+ private const string FORCE_TOKEN = "{{force}}";
+ public const string PYTHON_PACKAGE = "python";
+ private string _exePath = string.Empty;
+
+ private const string APP_NAME = "Python";
+ public const string PACKAGE_NAME_GROUP = "PkgName";
+ public static readonly Regex InstalledRegex = new Regex(@"Successfully installed", RegexOptions.Compiled);
+ public static readonly Regex UninstalledRegex = new Regex(@"Successfully uninstalled", RegexOptions.Compiled);
+ public static readonly Regex PackageNameRegex = new Regex(@"\s(?<{0}>[^-\s]*)-".format_with(PACKAGE_NAME_GROUP), RegexOptions.Compiled);
+ public static readonly Regex ErrorRegex = new Regex(@"Error:", RegexOptions.Compiled);
+ public static readonly Regex ErrorNotFoundRegex = new Regex(@"Could not find any downloads that", RegexOptions.Compiled);
+
+ private readonly IDictionary _listArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
+ private readonly IDictionary _installArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
+ private readonly IDictionary _upgradeArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
+ private readonly IDictionary _uninstallArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
+
+ public PythonService(ICommandExecutor commandExecutor, INugetService nugetService, IFileSystem fileSystem, IRegistryService registryService)
+ {
+ _commandExecutor = commandExecutor;
+ _nugetService = nugetService;
+ _fileSystem = fileSystem;
+ _registryService = registryService;
+ set_cmd_args_dictionaries();
+ }
+
+ ///
+ /// Set any command arguments dictionaries necessary for the service
+ ///
+ private void set_cmd_args_dictionaries()
+ {
+ set_list_dictionary(_listArguments);
+ set_install_dictionary(_installArguments);
+ set_upgrade_dictionary(_upgradeArguments);
+ set_uninstall_dictionary(_uninstallArguments);
+ }
+
+ ///
+ /// Sets list dictionary
+ ///
+ private void set_list_dictionary(IDictionary args)
+ {
+ set_common_args(args);
+ args.Add("_command_", new ExternalCommandArgument { ArgumentOption = "list", Required = true });
+ }
+
+ ///
+ /// Sets install dictionary
+ ///
+ private void set_install_dictionary(IDictionary args)
+ {
+ set_common_args(args);
+
+ args.Add("_command_", new ExternalCommandArgument { ArgumentOption = "install", Required = true });
+ args.Add("_package_name_", new ExternalCommandArgument
+ {
+ ArgumentOption = "",
+ ArgumentValue = PACKAGE_NAME_TOKEN,
+ QuoteValue = false,
+ UseValueOnly = true,
+ Required = true
+ });
+ }
+
+ ///
+ /// Sets install dictionary
+ ///
+ private void set_upgrade_dictionary(IDictionary args)
+ {
+ set_common_args(args);
+
+ args.Add("_command_", new ExternalCommandArgument { ArgumentOption = "install", Required = true });
+ args.Add("_upgrade_", new ExternalCommandArgument { ArgumentOption = "--upgrade", Required = true });
+ args.Add("_package_name_", new ExternalCommandArgument
+ {
+ ArgumentOption = "",
+ ArgumentValue = PACKAGE_NAME_TOKEN,
+ QuoteValue = false,
+ UseValueOnly = true,
+ Required = true
+ });
+ }
+
+ ///
+ /// Sets uninstall dictionary
+ ///
+ private void set_uninstall_dictionary(IDictionary args)
+ {
+ set_common_args(args);
+
+ args.Add("_command_", new ExternalCommandArgument { ArgumentOption = "uninstall", Required = true });
+ args.Add("_confirm_", new ExternalCommandArgument { ArgumentOption = "-y", Required = true });
+ args.Add("_package_name_", new ExternalCommandArgument
+ {
+ ArgumentOption = "",
+ ArgumentValue = PACKAGE_NAME_TOKEN,
+ QuoteValue = false,
+ UseValueOnly = true,
+ Required = true
+ });
+ }
+
+ private void set_common_args(IDictionary args)
+ {
+ args.Add("_loglevel_", new ExternalCommandArgument
+ {
+ ArgumentOption = "",
+ ArgumentValue = LOG_LEVEL_TOKEN,
+ QuoteValue = false,
+ UseValueOnly = true,
+ Required = true
+ });
+
+ args.Add("_force_", new ExternalCommandArgument
+ {
+ ArgumentOption = "",
+ ArgumentValue = FORCE_TOKEN,
+ QuoteValue = false,
+ UseValueOnly = true,
+ Required = true
+ });
+
+
+ }
+
+ public SourceType SourceType
+ {
+ get { return SourceType.python; }
+ }
+
+ public void ensure_source_app_installed(ChocolateyConfiguration config, Action ensureAction)
+ {
+ //ensure at least python 2.7.9 is installed
+ var python = _fileSystem.get_executable_path("python");
+ //python -V
+
+ if (python.is_equal_to("python"))
+ {
+ var runnerConfig = new ChocolateyConfiguration
+ {
+ Sources = ApplicationParameters.PackagesLocation,
+ Debug = config.Debug,
+ Force = config.Force,
+ Verbose = config.Verbose,
+ CommandExecutionTimeoutSeconds = config.CommandExecutionTimeoutSeconds,
+ CacheLocation = config.CacheLocation,
+ RegularOutput = config.RegularOutput,
+ PromptForConfirmation = false,
+ AcceptLicense = true,
+ };
+ runnerConfig.ListCommand.LocalOnly = true;
+
+ var localPackages = _nugetService.list_run(runnerConfig, logResults: false);
+
+ if (!localPackages.ContainsKey(PYTHON_PACKAGE))
+ {
+ runnerConfig.PackageNames = PYTHON_PACKAGE;
+ runnerConfig.Sources = ApplicationParameters.ChocolateyCommunityFeedSource;
+
+ var prompt = config.PromptForConfirmation;
+ config.PromptForConfirmation = false;
+ _nugetService.install_run(runnerConfig, ensureAction.Invoke);
+ config.PromptForConfirmation = prompt;
+ }
+ }
+ }
+
+ public void set_executable_path_if_not_set()
+ {
+ if (!string.IsNullOrWhiteSpace(_exePath)) return;
+
+ var python = _fileSystem.get_executable_path("python");
+
+ var pipPath = string.Empty;
+ if (!python.is_equal_to("python"))
+ {
+ pipPath = _fileSystem.combine_paths(_fileSystem.get_directory_name(python), "Scripts", "pip.exe");
+ if (_fileSystem.file_exists(pipPath))
+ {
+ _exePath = pipPath;
+ return;
+ }
+ }
+
+ var topLevelPath = string.Empty;
+ var python34PathKey = _registryService.get_key(RegistryHive.LocalMachine, "SOFTWARE\\Python\\PythonCore\\3.4\\InstallPath");
+ if (python34PathKey != null)
+ {
+ topLevelPath = python34PathKey.GetValue("", string.Empty).to_string();
+ }
+ if (string.IsNullOrWhiteSpace(topLevelPath))
+ {
+ var python27PathKey = _registryService.get_key(RegistryHive.LocalMachine, "SOFTWARE\\Python\\PythonCore\\2.7\\InstallPath");
+ if (python27PathKey != null)
+ {
+ topLevelPath = python27PathKey.GetValue("", string.Empty).to_string();
+ }
+ }
+
+ if (string.IsNullOrWhiteSpace(topLevelPath))
+ {
+ var binRoot = Environment.GetEnvironmentVariable("ChocolateyBinRoot");
+ if (string.IsNullOrWhiteSpace(binRoot)) binRoot = "c:\\tools";
+
+ topLevelPath = _fileSystem.combine_paths(binRoot, "python");
+ }
+
+ pipPath = _fileSystem.combine_paths(_fileSystem.get_directory_name(topLevelPath), "Scripts", "pip.exe");
+ if (_fileSystem.file_exists(pipPath))
+ {
+ _exePath = pipPath;
+ }
+
+ if (string.IsNullOrWhiteSpace(_exePath)) throw new FileNotFoundException("Unable to find pip");
+ }
+
+ public string build_args(ChocolateyConfiguration config, IDictionary argsDictionary)
+ {
+ var args = ExternalCommandArgsBuilder.build_arguments(config, argsDictionary);
+
+ args = args.Replace(LOG_LEVEL_TOKEN, config.Debug ? "-vvv" : "");
+
+ if (config.CommandName.is_equal_to("intall"))
+ {
+ args = args.Replace(FORCE_TOKEN, config.Force ? "--ignore-installed" : "");
+ }
+ else if (config.CommandName.is_equal_to("upgrade"))
+ {
+ args = args.Replace(FORCE_TOKEN, config.Force ? "--force-reinstall" : "");
+ }
+ else
+ {
+ args = args.Replace(FORCE_TOKEN, "");
+ }
+
+ return args;
+ }
+
+ public void list_noop(ChocolateyConfiguration config)
+ {
+ set_executable_path_if_not_set();
+ var args = build_args(config, _listArguments);
+ this.Log().Info("Would have run '{0} {1}'".format_with(_exePath, args));
+ }
+
+ public ConcurrentDictionary list_run(ChocolateyConfiguration config, bool logResults)
+ {
+ set_executable_path_if_not_set();
+ var args = build_args(config, _listArguments);
+ var packageResults = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase);
+
+ Environment.ExitCode = _commandExecutor.execute(
+ _exePath,
+ args,
+ config.CommandExecutionTimeoutSeconds,
+ stdOutAction: (s, e) =>
+ {
+ var logMessage = e.Data;
+ if (string.IsNullOrWhiteSpace(logMessage)) return;
+ if (logResults)
+ {
+ this.Log().Info(e.Data);
+ }
+ else
+ {
+ this.Log().Debug(() => "[{0}] {1}".format_with(APP_NAME, logMessage));
+ }
+ },
+ stdErrAction: (s, e) =>
+ {
+ if (string.IsNullOrWhiteSpace(e.Data)) return;
+ this.Log().Error(() => "{0}".format_with(e.Data));
+ },
+ updateProcessPath: false
+ );
+
+ return packageResults;
+ }
+
+ public void install_noop(ChocolateyConfiguration config, Action continueAction)
+ {
+ set_executable_path_if_not_set();
+ var args = build_args(config, _installArguments);
+ this.Log().Info("Would have run '{0} {1}'".format_with(_exePath, args));
+ }
+
+ public ConcurrentDictionary install_run(ChocolateyConfiguration config, Action continueAction)
+ {
+ set_executable_path_if_not_set();
+ var args = build_args(config, _installArguments);
+ var packageResults = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase);
+
+ foreach (var packageToInstall in config.PackageNames.Split(new[] { ApplicationParameters.PackageNamesSeparator }, StringSplitOptions.RemoveEmptyEntries))
+ {
+ var pkgName = packageToInstall;
+ if (!string.IsNullOrWhiteSpace(config.Version))
+ {
+ pkgName = "{0}=={1}".format_with(packageToInstall, config.Version);
+ }
+ var argsForPackage = args.Replace(PACKAGE_NAME_TOKEN, pkgName);
+
+ var exitCode = _commandExecutor.execute(
+ _exePath,
+ argsForPackage,
+ config.CommandExecutionTimeoutSeconds,
+ (s, e) =>
+ {
+ var logMessage = e.Data;
+ if (string.IsNullOrWhiteSpace(logMessage)) return;
+ this.Log().Info(() => " [{0}] {1}".format_with(APP_NAME, logMessage));
+
+ if (ErrorRegex.IsMatch(logMessage) || ErrorNotFoundRegex.IsMatch(logMessage))
+ {
+ var results = packageResults.GetOrAdd(packageToInstall, new PackageResult(packageToInstall, null, null));
+ results.Messages.Add(new ResultMessage(ResultType.Error, logMessage));
+ }
+
+ if (InstalledRegex.IsMatch(logMessage))
+ {
+ var packageName = get_value_from_output(logMessage, PackageNameRegex, PACKAGE_NAME_GROUP);
+ var results = packageResults.GetOrAdd(packageName, new PackageResult(packageName, null, null));
+ results.Messages.Add(new ResultMessage(ResultType.Note, packageName));
+ this.Log().Info(ChocolateyLoggers.Important, " {0} has been installed successfully.".format_with(string.IsNullOrWhiteSpace(packageName) ? packageToInstall : packageName));
+ }
+ },
+ (s, e) =>
+ {
+ var logMessage = e.Data;
+ if (string.IsNullOrWhiteSpace(logMessage)) return;
+ this.Log().Error("[{0}] {1}".format_with(APP_NAME, logMessage));
+
+ if (ErrorRegex.IsMatch(logMessage) || ErrorNotFoundRegex.IsMatch(logMessage))
+ {
+ var results = packageResults.GetOrAdd(packageToInstall, new PackageResult(packageToInstall, null, null));
+ results.Messages.Add(new ResultMessage(ResultType.Error, logMessage));
+ }
+ },
+ updateProcessPath: false
+ );
+
+ if (exitCode != 0)
+ {
+ Environment.ExitCode = exitCode;
+ }
+ }
+
+ return packageResults;
+ }
+
+ public ConcurrentDictionary upgrade_noop(ChocolateyConfiguration config, Action continueAction)
+ {
+ set_executable_path_if_not_set();
+ var args = build_args(config, _upgradeArguments);
+ this.Log().Info("Would have run '{0} {1}'".format_with(_exePath, args));
+ return new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase);
+ }
+
+ public ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction)
+ {
+ set_executable_path_if_not_set();
+ var args = build_args(config, _upgradeArguments);
+ var packageResults = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase);
+
+ foreach (var packageToInstall in config.PackageNames.Split(new[] { ApplicationParameters.PackageNamesSeparator }, StringSplitOptions.RemoveEmptyEntries))
+ {
+ var pkgName = packageToInstall;
+ if (!string.IsNullOrWhiteSpace(config.Version))
+ {
+ pkgName = "{0}=={1}".format_with(packageToInstall, config.Version);
+ }
+
+ var argsForPackage = args.Replace(PACKAGE_NAME_TOKEN, pkgName);
+
+ var exitCode = _commandExecutor.execute(
+ _exePath,
+ argsForPackage,
+ config.CommandExecutionTimeoutSeconds,
+ (s, e) =>
+ {
+ var logMessage = e.Data;
+ if (string.IsNullOrWhiteSpace(logMessage)) return;
+ this.Log().Info(() => " [{0}] {1}".format_with(APP_NAME, logMessage));
+
+ if (ErrorRegex.IsMatch(logMessage) || ErrorNotFoundRegex.IsMatch(logMessage))
+ {
+ var results = packageResults.GetOrAdd(packageToInstall, new PackageResult(packageToInstall, null, null));
+ results.Messages.Add(new ResultMessage(ResultType.Error, logMessage));
+ }
+
+ if (InstalledRegex.IsMatch(logMessage))
+ {
+ var packageName = get_value_from_output(logMessage, PackageNameRegex, PACKAGE_NAME_GROUP);
+ var results = packageResults.GetOrAdd(packageName, new PackageResult(packageName, null, null));
+ results.Messages.Add(new ResultMessage(ResultType.Note, packageName));
+ this.Log().Info(ChocolateyLoggers.Important, " {0} has been installed successfully.".format_with(string.IsNullOrWhiteSpace(packageName) ? packageToInstall : packageName));
+ }
+ },
+ (s, e) =>
+ {
+ var logMessage = e.Data;
+ if (string.IsNullOrWhiteSpace(logMessage)) return;
+ this.Log().Error("[{0}] {1}".format_with(APP_NAME, logMessage));
+
+ if (ErrorRegex.IsMatch(logMessage) || ErrorNotFoundRegex.IsMatch(logMessage))
+ {
+ var results = packageResults.GetOrAdd(packageToInstall, new PackageResult(packageToInstall, null, null));
+ results.Messages.Add(new ResultMessage(ResultType.Error, logMessage));
+ }
+ },
+ updateProcessPath: false
+ );
+
+ if (exitCode != 0)
+ {
+ Environment.ExitCode = exitCode;
+ }
+ }
+
+ return packageResults;
+ }
+
+ public void uninstall_noop(ChocolateyConfiguration config, Action continueAction)
+ {
+ set_executable_path_if_not_set();
+ var args = build_args(config, _uninstallArguments);
+ this.Log().Info("Would have run '{0} {1}'".format_with(_exePath, args));
+ }
+
+ public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction)
+ {
+ set_executable_path_if_not_set();
+ var args = build_args(config, _uninstallArguments);
+ var packageResults = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase);
+
+ foreach (var packageToInstall in config.PackageNames.Split(new[] { ApplicationParameters.PackageNamesSeparator }, StringSplitOptions.RemoveEmptyEntries))
+ {
+ var argsForPackage = args.Replace(PACKAGE_NAME_TOKEN, packageToInstall);
+
+ var exitCode = _commandExecutor.execute(
+ _exePath,
+ argsForPackage,
+ config.CommandExecutionTimeoutSeconds,
+ (s, e) =>
+ {
+ var logMessage = e.Data;
+ if (string.IsNullOrWhiteSpace(logMessage)) return;
+ this.Log().Info(() => " [{0}] {1}".format_with(APP_NAME, logMessage));
+
+ if (ErrorRegex.IsMatch(logMessage) || ErrorNotFoundRegex.IsMatch(logMessage))
+ {
+ var results = packageResults.GetOrAdd(packageToInstall, new PackageResult(packageToInstall, null, null));
+ results.Messages.Add(new ResultMessage(ResultType.Error, packageToInstall));
+ }
+
+ if (UninstalledRegex.IsMatch(logMessage))
+ {
+ var packageName = get_value_from_output(logMessage, PackageNameRegex, PACKAGE_NAME_GROUP);
+ var results = packageResults.GetOrAdd(packageName, new PackageResult(packageName, null, null));
+ results.Messages.Add(new ResultMessage(ResultType.Note, packageName));
+ this.Log().Info(ChocolateyLoggers.Important, " {0} has been uninstalled successfully.".format_with(string.IsNullOrWhiteSpace(packageName) ? packageToInstall : packageName));
+ }
+ },
+ (s, e) =>
+ {
+ var logMessage = e.Data;
+ if (string.IsNullOrWhiteSpace(logMessage)) return;
+ this.Log().Error("[{0}] {1}".format_with(APP_NAME, logMessage));
+
+ if (ErrorRegex.IsMatch(logMessage) || ErrorNotFoundRegex.IsMatch(logMessage))
+ {
+ var results = packageResults.GetOrAdd(packageToInstall, new PackageResult(packageToInstall, null, null));
+ results.Messages.Add(new ResultMessage(ResultType.Error, logMessage));
+ }
+ },
+ updateProcessPath: false
+ );
+
+ if (exitCode != 0)
+ {
+ Environment.ExitCode = exitCode;
+ }
+ }
+
+ return packageResults;
+ }
+
+ ///
+ /// Grabs a value from the output based on the regex.
+ ///
+ /// The output.
+ /// The regex.
+ /// Name of the group.
+ ///
+ private static string get_value_from_output(string output, Regex regex, string groupName)
+ {
+ var matchGroup = regex.Match(output).Groups[groupName];
+ if (matchGroup != null)
+ {
+ return matchGroup.Value;
+ }
+
+ return string.Empty;
+ }
+ }
+}
\ No newline at end of file