Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Pry method patcher #892

Merged
merged 9 commits into from

1 participant

@ConradIrwin
Owner

I want to be able to redefine methods from the pry-rescue gem. At the moment I "can" by importing and sub-classing Pry::Commands::Edit::MethodPatcher, but it's nicer to provide an actual API for this in Pry::Method#redefine.

I want to eventually expand the scope of this to include saving methods too (but it's quite different from the save-source branch).

Any thoughts?

@ConradIrwin ConradIrwin merged commit 9c1f814 into from
@ConradIrwin ConradIrwin deleted the branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 28, 2013
  1. @ConradIrwin
  2. @ConradIrwin
  3. @ConradIrwin
  4. @ConradIrwin
  5. @ConradIrwin
  6. @ConradIrwin

    s/code_object/method

    ConradIrwin authored
  7. @ConradIrwin

    Modify methods using Pry::Method#redefine

    ConradIrwin authored
    I considered calling redefine source=, but Pry::Method objects are not
    designed to be mutable.
  8. @ConradIrwin

    Move cache reading into Pry::Code for consistency

    ConradIrwin authored
    This necessitates ensuring that the wrapping doesn't add any newlines
Commits on Mar 30, 2013
  1. @ConradIrwin

    Remove broken require

    ConradIrwin authored
This page is out of date. Refresh to see the latest.
View
2  lib/pry/code.rb
@@ -61,6 +61,8 @@ class << self
def from_file(filename, code_type = type_from_filename(filename))
code = if filename == Pry.eval_path
Pry.line_buffer.drop(1)
+ elsif Pry::Method::Patcher.code_for(filename)
+ Pry::Method::Patcher.code_for(filename)
else
File.read(abs_path(filename))
end
View
3  lib/pry/commands/edit.rb
@@ -1,6 +1,5 @@
class Pry
class Command::Edit < Pry::ClassCommand
- require 'pry/commands/edit/method_patcher'
require 'pry/commands/edit/exception_patcher'
require 'pry/commands/edit/file_and_line_locator'
@@ -83,7 +82,7 @@ def apply_runtime_patch
ExceptionPatcher.new(_pry_, state, file_and_line_for_current_exception).perform_patch
else
if code_object.is_a?(Pry::Method)
- MethodPatcher.new(_pry_, code_object).perform_patch
+ code_object.redefine Pry::Editor.edit_tempfile_with_content(code_object.source)
else
raise NotImplementedError, "Cannot yet patch #{code_object} objects!"
end
View
47 lib/pry/method.rb
@@ -19,6 +19,7 @@ def Method(obj)
class Method
require 'pry/method/weird_method_locator'
require 'pry/method/disowned'
+ require 'pry/method/patcher'
extend Helpers::BaseHelpers
include Helpers::BaseHelpers
@@ -263,26 +264,18 @@ def name_with_owner
def source
@source ||= case source_type
when :c
- info = pry_doc_info
- if info and info.source
- code = strip_comments_from_c_code(info.source)
- end
+ c_source
when :ruby
- # clone of MethodSource.source_helper that knows to use our
- # hacked version of source_location for rbx core methods, and
- # our input buffer for methods defined in (pry)
- file, line = *source_location
- raise SourceNotFoundError, "Could not locate source for #{name_with_owner}!" unless file
-
- begin
- code = Pry::Code.from_file(file).expression_at(line)
- rescue SyntaxError => e
- raise MethodSource::SourceNotFoundError.new(e.message)
- end
- strip_leading_whitespace(code)
+ ruby_source
end
end
+ # Update the live copy of the method's source.
+ def redefine(source)
+ Patcher.new(self).patch_in_ram source
+ Pry::Method(owner.instance_method(name))
+ end
+
# Can we get the source code for this method?
# @return [Boolean]
def source?
@@ -547,5 +540,27 @@ def method_name_from_first_line(first_ln)
nil
end
+
+ def c_source
+ info = pry_doc_info
+ if info and info.source
+ strip_comments_from_c_code(info.source)
+ end
+ end
+
+ def ruby_source
+ # clone of MethodSource.source_helper that knows to use our
+ # hacked version of source_location for rbx core methods, and
+ # our input buffer for methods defined in (pry)
+ file, line = *source_location
+ raise SourceNotFoundError, "Could not locate source for #{name_with_owner}!" unless file
+
+ begin
+ code = Pry::Code.from_file(file).expression_at(line)
+ rescue SyntaxError => e
+ raise MethodSource::SourceNotFoundError.new(e.message)
+ end
+ strip_leading_whitespace(code)
+ end
end
end
View
72 lib/pry/commands/edit/method_patcher.rb → lib/pry/method/patcher.rb
@@ -1,37 +1,38 @@
class Pry
- class Command::Edit
- class MethodPatcher
- attr_accessor :_pry_
- attr_accessor :code_object
+ class Method
+ class Patcher
+ attr_accessor :method
- def initialize(_pry_, code_object)
- @_pry_ = _pry_
- @code_object = code_object
+ @@source_cache = {}
+
+ def initialize(method)
+ @method = method
+ end
+
+ def self.code_for(filename)
+ @@source_cache[filename]
end
# perform the patch
- def perform_patch
- if code_object.alias?
+ def patch_in_ram(source)
+ if method.alias?
with_method_transaction do
- _pry_.evaluate_ruby patched_code
+ redefine source
end
else
- _pry_.evaluate_ruby patched_code
+ redefine source
end
end
private
- def patched_code
- @patched_code ||= wrap(Pry::Editor.edit_tempfile_with_content(adjusted_lines))
+ def redefine(source)
+ @@source_cache[cache_key] = source
+ TOPLEVEL_BINDING.eval wrap(source), cache_key
end
- # The method code adjusted so that the first line is rewritten
- # so that def self.foo --> def foo
- def adjusted_lines
- lines = code_object.source.lines.to_a
- lines[0] = definition_line_for_owner(lines.first)
- lines
+ def cache_key
+ "pry-redefined(0x#{method.owner.object_id.to_s(16)}##{method.name})"
end
# Run some code ensuring that at the end target#meth_name will not have changed.
@@ -45,17 +46,17 @@ def adjusted_lines
# @param [Module] target The owner of the method
def with_method_transaction
- temp_name = "__pry_#{code_object.original_name}__"
- co = code_object
- code_object.owner.class_eval do
- alias_method temp_name, co.original_name
+ temp_name = "__pry_#{method.original_name}__"
+ method = self.method
+ method.owner.class_eval do
+ alias_method temp_name, method.original_name
yield
- alias_method co.name, co.original_name
- alias_method co.original_name, temp_name
+ alias_method method.name, method.original_name
+ alias_method method.original_name, temp_name
end
ensure
- co.send(:remove_method, temp_name) rescue nil
+ method.send(:remove_method, temp_name) rescue nil
end
# Update the definition line so that it can be eval'd directly on the Method's
@@ -70,11 +71,11 @@ def with_method_transaction
#
# @param String The original definition line. e.g. def self.foo(bar, baz=1)
# @return String The new definition line. e.g. def foo(bar, baz=1)
- def definition_line_for_owner(line)
- if line =~ /^def (?:.*?\.)?#{Regexp.escape(code_object.original_name)}(?=[\(\s;]|$)/
- "def #{code_object.original_name}#{$'}"
+ def definition_for_owner(line)
+ if line =~ /\Adef (?:.*?\.)?#{Regexp.escape(method.original_name)}(?=[\(\s;]|$)/
+ "def #{method.original_name}#{$'}"
else
- raise CommandError, "Could not find original `def #{code_object.original_name}` line to patch."
+ raise CommandError, "Could not find original `def #{method.original_name}` line to patch."
end
end
@@ -87,15 +88,16 @@ def wrap(source)
# Update the source code so that when it has the right owner when eval'd.
#
- # This (combined with definition_line_for_owner) is backup for the case that
+ # This (combined with definition_for_owner) is backup for the case that
# wrap_for_nesting fails, to ensure that the method will stil be defined in
# the correct place.
#
# @param [String] source The source to wrap
# @return [String]
def wrap_for_owner(source)
- Pry.current[:pry_owner] = code_object.owner
- "Pry.current[:pry_owner].class_eval do\n#{source}\nend"
+ Pry.current[:pry_owner] = method.owner
+ owner_source = definition_for_owner(source)
+ "Pry.current[:pry_owner].class_eval do; #{owner_source}\nend"
end
# Update the new source code to have the correct Module.nesting.
@@ -111,9 +113,9 @@ def wrap_for_owner(source)
# @param [String] source The source to wrap.
# @return [String]
def wrap_for_nesting(source)
- nesting = Pry::Code.from_file(code_object.source_file).nesting_at(code_object.source_line)
+ nesting = Pry::Code.from_file(method.source_file).nesting_at(method.source_line)
- (nesting + [source] + nesting.map{ "end" } + [""]).join("\n")
+ (nesting + [source] + nesting.map{ "end" } + [""]).join(";")
rescue Pry::Indent::UnparseableNestingError
source
end
View
27 spec/method/patcher_spec.rb
@@ -0,0 +1,27 @@
+require 'helper'
+
+describe Pry::Method::Patcher do
+
+ before do
+ @x = Object.new
+ def @x.test; :before; end
+ @method = Pry::Method(@x.method(:test))
+ end
+
+ it "should change the behaviour of the method" do
+ @x.test.should == :before
+ @method.redefine "def @x.test; :after; end\n"
+ @x.test.should == :after
+ end
+
+ it "should return a new method with new source" do
+ @method.source.strip.should == "def @x.test; :before; end"
+ @method.redefine("def @x.test; :after; end\n").
+ source.strip.should == "def @x.test; :after; end"
+ end
+
+ it "should change the source of new Pry::Method objects" do
+ @method.redefine "def @x.test; :after; end\n"
+ Pry::Method(@x.method(:test)).source.strip.should == "def @x.test; :after; end"
+ end
+end
Something went wrong with that request. Please try again.