Skip to content

Commit

Permalink
Restore plugin autoloading as deprecated opt-in
Browse files Browse the repository at this point in the history
  • Loading branch information
bf4 committed Feb 16, 2021
1 parent cee7b4d commit d02afed
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 10 deletions.
1 change: 1 addition & 0 deletions lib/pry.rb
Expand Up @@ -13,6 +13,7 @@

require 'pry/basic_object'
require 'pry/prompt'
require 'pry/plugins'
require 'pry/code_object'
require 'pry/exceptions'
require 'pry/hooks'
Expand Down
30 changes: 20 additions & 10 deletions lib/pry/cli.rb
Expand Up @@ -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 ||= []
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -150,16 +167,9 @@ def start(opts)
Pry.config.should_load_local_rc = false
end

on :s, "select-plugin=", "Only load specified plugin (and no others)." do |_plugin_name|
warn "The --select-plugin option is deprecated and has no effect"
end

on :d, "disable-plugin=", "Disable a specific plugin." do |_plugin_name|
warn "The --disable-plugin option is deprecated and has no effect"
end

on "no-plugins", "Suppress loading of plugins." do
warn "The --no-plugins option is deprecated and has no effect"
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
Expand Down
4 changes: 4 additions & 0 deletions lib/pry/config.rb
Expand Up @@ -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

Expand Down Expand Up @@ -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,
Expand Down
139 changes: 139 additions & 0 deletions 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
5 changes: 5 additions & 0 deletions lib/pry/pry_class.rb
Expand Up @@ -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=,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions spec/cli_spec.rb
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions spec/config_spec.rb
Expand Up @@ -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) }
Expand Down

0 comments on commit d02afed

Please sign in to comment.