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

Conrad Irwin
Conrad Irwin
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?

Conrad Irwin ConradIrwin merged commit 9c1f814 into from
Conrad Irwin 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. Conrad Irwin
  2. Conrad Irwin
  3. Conrad Irwin
  4. Conrad Irwin
  5. Conrad Irwin
  6. Conrad Irwin

    s/code_object/method

    ConradIrwin authored
  7. Conrad Irwin

    Modify methods using Pry::Method#redefine

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

    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. Conrad Irwin

    Remove broken require

    ConradIrwin authored
This page is out of date. Refresh to see the latest.
2  lib/pry/code.rb
View
@@ -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
3  lib/pry/commands/edit.rb
View
@@ -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
47 lib/pry/method.rb
View
@@ -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
72 lib/pry/commands/edit/method_patcher.rb → lib/pry/method/patcher.rb
View
@@ -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
27 spec/method/patcher_spec.rb
View
@@ -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.