From 83ede1d856e90f243c43fac2a979dc63f917d312 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Mon, 15 Feb 2021 18:16:16 -0600 Subject: [PATCH 1/2] Restore plugin autoloading as deprecated opt-in --- lib/pry.rb | 1 + lib/pry/cli.rb | 22 +++++++ lib/pry/config.rb | 4 ++ lib/pry/plugins.rb | 139 +++++++++++++++++++++++++++++++++++++++++++ lib/pry/pry_class.rb | 5 ++ spec/cli_spec.rb | 15 +++++ spec/config_spec.rb | 1 + 7 files changed, 187 insertions(+) create mode 100644 lib/pry/plugins.rb diff --git a/lib/pry.rb b/lib/pry.rb index 7ead087b9..2fddd0032 100644 --- a/lib/pry.rb +++ b/lib/pry.rb @@ -13,6 +13,7 @@ require 'pry/basic_object' require 'pry/prompt' +require 'pry/plugins' require 'pry/code_object' require 'pry/exceptions' require 'pry/hooks' diff --git a/lib/pry/cli.rb b/lib/pry/cli.rb index 8d56794d4..1a80812ef 100644 --- a/lib/pry/cli.rb +++ b/lib/pry/cli.rb @@ -35,6 +35,14 @@ def add_options(&block) self end + # Bring in options defined in plugins + def add_plugin_options + Pry::Warning.warn "Pry.plugins is deprecated and will be removed entirely" + Pry.plugins.values.each(&:load_cli_options) + + self + end + # Add a block responsible for processing parsed options. def add_option_processor(&block) self.option_processors ||= [] @@ -117,6 +125,15 @@ def start(opts) end end +# Bring in options defined by plugins +Pry::Slop.new do + on "enable-plugins" do + Pry.config.should_load_plugins = true + end +end.parse(ARGV.dup) + +Pry::CLI.add_plugin_options if Pry.config.should_load_plugins + # The default Pry command line options (before plugin options are included) Pry::CLI.add_options do banner( @@ -162,6 +179,11 @@ def start(opts) warn "The --no-plugins option is deprecated and has no effect" end + on :p, "enable-plugins", "Opt-in to auto loading of plugins." do + warn "The --enable-plugins option is deprecated" + Pry.config.should_load_plugins = true + end + on "plugins", "List installed plugins." do puts "Installed Plugins:" puts "--" diff --git a/lib/pry/config.rb b/lib/pry/config.rb index 48c4089da..d8bc7f4be 100644 --- a/lib/pry/config.rb +++ b/lib/pry/config.rb @@ -73,6 +73,9 @@ class Config # @return [Boolean] whether the local ./.pryrc should be loaded attribute :should_load_local_rc + # @return [Boolean] + attribute :should_load_plugins + # @return [Boolean] whether to load files specified with the -r flag attribute :should_load_requires @@ -193,6 +196,7 @@ def initialize output_prefix: '=> ', requires: [], should_load_requires: true, + should_load_plugins: false, windows_console_warning: true, control_d_handler: Pry::ControlDHandler.method(:default), memory_size: 100, diff --git a/lib/pry/plugins.rb b/lib/pry/plugins.rb new file mode 100644 index 000000000..c29894d4d --- /dev/null +++ b/lib/pry/plugins.rb @@ -0,0 +1,139 @@ +# frozen_string_literal: true + +require 'ostruct' + +class Pry + class PluginManager + PRY_PLUGIN_PREFIX = /^pry-/.freeze + + # Placeholder when no associated gem found, displays warning + class NoPlugin + def initialize(name) + @name = name + end + + def method_missing(*) + warn "Warning: The plugin '#{@name}' was not found! (no gem found)" + super + end + + def respond_to_missing?(*) + false + end + end + + class Plugin + attr_accessor :name, :gem_name, :enabled, :spec, :active + + def initialize(name, gem_name, spec, enabled) + @name = name + @gem_name = gem_name + @enabled = enabled + @spec = spec + end + + # Disable a plugin. (prevents plugin from being loaded, cannot + # disable an already activated plugin) + def disable! + self.enabled = false + end + + # Enable a plugin. (does not load it immediately but puts on + # 'white list' to be loaded) + def enable! + self.enabled = true + end + + # Load the Command line options defined by this plugin (if they exist) + def load_cli_options + cli_options_file = File.join(spec.full_gem_path, "lib/#{spec.name}/cli.rb") + return unless File.exist?(cli_options_file) + + if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.4.4") + cli_options_file = File.realpath(cli_options_file) + end + require cli_options_file + end + + # Activate the plugin (require the gem - enables/loads the + # plugin immediately at point of call, even if plugin is + # disabled) + # Does not reload plugin if it's already active. + def activate! + # Create the configuration object for the plugin. + Pry.config.send("#{gem_name.tr('-', '_')}=", OpenStruct.new) + + begin + require gem_name unless active? + rescue LoadError => e + warn "Found plugin #{gem_name}, but could not require '#{gem_name}'" + warn e + rescue StandardError => e + warn "require '#{gem_name}' # Failed, saying: #{e}" + end + + self.active = true + self.enabled = true + end + + alias active? active + alias enabled? enabled + + def supported? + pry_version = Gem::Version.new(VERSION) + spec.dependencies.each do |dependency| + if dependency.name == "pry" + return dependency.requirement.satisfied_by?(pry_version) + end + end + true + end + end + + def initialize + @plugins = [] + end + + # Find all installed Pry plugins and store them in an internal array. + def locate_plugins + gem_list.each do |gem| + next if gem.name !~ PRY_PLUGIN_PREFIX + + plugin_name = gem.name.split('-', 2).last + plugin = Plugin.new(plugin_name, gem.name, gem, false) + @plugins << plugin.tap(&:enable!) if plugin.supported? && !plugin_located?(plugin) + end + @plugins + end + + # @return [Hash] A hash with all plugin names (minus the 'pry-') as + # keys and Plugin objects as values. + def plugins + h = Hash.new { |_, key| NoPlugin.new(key) } + @plugins.each do |plugin| + h[plugin.name] = plugin + end + h + end + + # Require all enabled plugins, disabled plugins are skipped. + def load_plugins + @plugins.each do |plugin| + plugin.activate! if plugin.enabled? + end + end + + private + + def plugin_located?(plugin) + @plugins.any? { |existing| existing.gem_name == plugin.gem_name } + end + + def gem_list + Gem.refresh + return Gem::Specification if Gem::Specification.respond_to?(:each) + + Gem.source_index.find_name('') + end + end +end diff --git a/lib/pry/pry_class.rb b/lib/pry/pry_class.rb index 118102dd1..80dda807a 100644 --- a/lib/pry/pry_class.rb +++ b/lib/pry/pry_class.rb @@ -24,6 +24,8 @@ class << self attr_accessor :last_internal_error attr_accessor :config + def_delegators :@plugin_manager, :plugins, :load_plugins, :locate_plugins + def_delegators( :@config, :input, :input=, :output, :output=, :commands, :commands=, :print, :print=, :exception_handler, :exception_handler=, @@ -140,6 +142,7 @@ def self.final_session_setup return if @session_finalized @session_finalized = true + load_plugins if Pry.config.should_load_plugins load_requires if Pry.config.should_load_requires load_history if Pry.config.history_load load_traps if Pry.config.should_trap_interrupts @@ -330,7 +333,9 @@ def self.reset_defaults # Basic initialization. def self.init + @plugin_manager ||= PluginManager.new reset_defaults + locate_plugins end # Return a `Binding` object for `target` or return `target` if it is diff --git a/spec/cli_spec.rb b/spec/cli_spec.rb index 39d6c7a26..93625f793 100644 --- a/spec/cli_spec.rb +++ b/spec/cli_spec.rb @@ -41,6 +41,21 @@ end end + describe ".add_plugin_options" do + it "returns self" do + expect(described_class.add_plugin_options).to eq(described_class) + end + + it "loads cli options of plugins" do + plugin_mock = double + expect(plugin_mock).to receive(:load_cli_options) + plugins = { 'pry-testplugin' => plugin_mock } + expect(Pry).to receive(:plugins).and_return(plugins) + + described_class.add_plugin_options + end + end + describe ".add_option_processor" do it "returns self" do expect(described_class.add_option_processor {}).to eq(described_class) diff --git a/spec/config_spec.rb b/spec/config_spec.rb index 4018a1dbf..f1cf5ea6c 100644 --- a/spec/config_spec.rb +++ b/spec/config_spec.rb @@ -28,6 +28,7 @@ specify { expect(subject.output_prefix).to be_a(String) } specify { expect(subject.requires).to be_an(Array) } specify { expect(subject.should_load_requires).to be(true).or be(false) } + specify { expect(subject.should_load_plugins).to be(true).or be(false) } specify { expect(subject.windows_console_warning).to be(true).or be(false) } specify { expect(subject.control_d_handler).to respond_to(:call) } specify { expect(subject.memory_size).to be_a(Numeric) } From 3538f235a9a976d15722de1da7a55fde61108f2f Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Mon, 15 Feb 2021 19:07:50 -0600 Subject: [PATCH 2/2] Note areas to remove --- lib/pry/cli.rb | 3 +++ lib/pry/config.rb | 1 + lib/pry/pry_class.rb | 8 +++++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/pry/cli.rb b/lib/pry/cli.rb index 1a80812ef..6b99aa43b 100644 --- a/lib/pry/cli.rb +++ b/lib/pry/cli.rb @@ -35,6 +35,7 @@ def add_options(&block) self end + # TODO: Remove add_plugin_options when removing plugin auto-loading # Bring in options defined in plugins def add_plugin_options Pry::Warning.warn "Pry.plugins is deprecated and will be removed entirely" @@ -125,6 +126,7 @@ def start(opts) end end +# TODO: Remove enable-plugins when removing plugin auto-loading # Bring in options defined by plugins Pry::Slop.new do on "enable-plugins" do @@ -132,6 +134,7 @@ def start(opts) end end.parse(ARGV.dup) +# TODO: Remove add_plugin_options when removing plugin auto-loading Pry::CLI.add_plugin_options if Pry.config.should_load_plugins # The default Pry command line options (before plugin options are included) diff --git a/lib/pry/config.rb b/lib/pry/config.rb index d8bc7f4be..e20321313 100644 --- a/lib/pry/config.rb +++ b/lib/pry/config.rb @@ -73,6 +73,7 @@ class Config # @return [Boolean] whether the local ./.pryrc should be loaded attribute :should_load_local_rc + # TODO: Remove should_load_plugins when removing plugin auto-loading # @return [Boolean] attribute :should_load_plugins diff --git a/lib/pry/pry_class.rb b/lib/pry/pry_class.rb index 80dda807a..b1b3c75b3 100644 --- a/lib/pry/pry_class.rb +++ b/lib/pry/pry_class.rb @@ -24,6 +24,7 @@ class << self attr_accessor :last_internal_error attr_accessor :config + # TODO: Remove def_delegators :@plugin_manager when removing plugin auto-loading def_delegators :@plugin_manager, :plugins, :load_plugins, :locate_plugins def_delegators( @@ -142,6 +143,7 @@ def self.final_session_setup return if @session_finalized @session_finalized = true + # TODO: Remove load_plugins when removing plugin auto-loading load_plugins if Pry.config.should_load_plugins load_requires if Pry.config.should_load_requires load_history if Pry.config.history_load @@ -333,9 +335,13 @@ def self.reset_defaults # Basic initialization. def self.init + # TODO: Remove PluginManager when removing plugin auto-loading @plugin_manager ||= PluginManager.new reset_defaults - locate_plugins + if Pry.config.should_load_plugins # rubocop:disable Style/GuardClause + # TODO: Remove locate_plugins when removing plugin auto-loading + locate_plugins + end end # Return a `Binding` object for `target` or return `target` if it is