require 'rubygems' require 'rubygems/command' require 'rubygems/commands/install_command' require 'rubygems/uninstaller' module Chronicle module ETL module Registry # Responsible for managing plugins available to chronicle-etl # # @todo Better validation for whether a gem is actually a plugin # @todo Add ways to load a plugin that don't require a gem on rubygems.org module PluginRegistry # Does this plugin exist? def self.exists?(name) # TODO: implement this. Could query rubygems.org or use a hardcoded # list somewhere true end # All versions of all plugins currently installed def self.all_installed # TODO: add check for chronicle-etl dependency Gem::Specification.filter { |s| s.name.match(/^chronicle-/) && s.name != "chronicle-etl" } end # Latest version of each installed plugin def self.all_installed_latest all_installed.group_by(&:name) .transform_values { |versions| versions.sort_by(&:version).reverse.first } .values end # Check whether a given plugin is installed def self.installed?(name) gem_name = "chronicle-#{name}" all_installed.map(&:name).include?(gem_name) end # Activate a plugin with given name by `require`ing it def self.activate(name) # By default, activates the latest available version of a gem # so don't have to run Kernel#gem separately require "chronicle/#{name}" rescue Gem::ConflictError => e # TODO: figure out if there's more we can do here raise Chronicle::ETL::PluginConflictError.new(name), "Plugin '#{name}' couldn't be loaded. #{e.message}" rescue StandardError, LoadError => e # StandardError to catch random non-loading problems that might occur # when requiring the plugin (eg class macro invoked the wrong way) # TODO: decide if this should be separated raise Chronicle::ETL::PluginLoadError.new(name), "Plugin '#{name}' couldn't be loaded" end # Install a plugin to local gems def self.install(name) return if installed?(name) gem_name = "chronicle-#{name}" raise(Chronicle::ETL::PluginNotAvailableError.new(gem_name), "Plugin #{name} doesn't exist") unless exists?(gem_name) Gem::DefaultUserInteraction.ui = Gem::SilentUI.new Gem.install(gem_name) activate(name) rescue Gem::UnsatisfiableDependencyError # TODO: we need to catch a lot more than this here raise Chronicle::ETL::PluginNotAvailableError.new(name), "Plugin #{name} could not be installed." end # Uninstall a plugin def self.uninstall(name) gem_name = "chronicle-#{name}" Gem::DefaultUserInteraction.ui = Gem::SilentUI.new uninstaller = Gem::Uninstaller.new(gem_name) uninstaller.uninstall rescue Gem::InstallError # TODO: strengthen this exception handling raise(Chronicle::ETL::PluginError.new(name), "Plugin #{name} wasn't uninstalled") end end end end end