Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restore plugin autoloading as deprecated opt-in #2177

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
25 changes: 25 additions & 0 deletions lib/pry/cli.rb
Expand Up @@ -35,6 +35,15 @@ 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"
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 +126,17 @@ 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
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure best way to test this

Pry.config.should_load_plugins = true
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)
Pry::CLI.add_options do
banner(
Expand Down Expand Up @@ -162,6 +182,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 "--"
Expand Down
5 changes: 5 additions & 0 deletions lib/pry/config.rb
Expand Up @@ -73,6 +73,10 @@ 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

# @return [Boolean] whether to load files specified with the -r flag
attribute :should_load_requires

Expand Down Expand Up @@ -193,6 +197,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
11 changes: 11 additions & 0 deletions lib/pry/pry_class.rb
Expand Up @@ -24,6 +24,9 @@ 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(
:@config, :input, :input=, :output, :output=, :commands,
:commands=, :print, :print=, :exception_handler, :exception_handler=,
Expand Down Expand Up @@ -140,6 +143,8 @@ 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
load_traps if Pry.config.should_trap_interrupts
Expand Down Expand Up @@ -330,7 +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
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
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