Class: Pry::Method::Patcher

Inherits:
Object show all
Defined in:
lib/pry/method/patcher.rb

Constant Summary collapse

@@source_cache =
{}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(method) ⇒ Patcher

Returns a new instance of Patcher



8
9
10
# File 'lib/pry/method/patcher.rb', line 8

def initialize(method)
  @method = method
end

Instance Attribute Details

#methodObject

Returns the value of attribute method



4
5
6
# File 'lib/pry/method/patcher.rb', line 4

def method
  @method
end

Class Method Details

.code_for(filename) ⇒ Object



12
13
14
# File 'lib/pry/method/patcher.rb', line 12

def self.code_for(filename)
  @@source_cache[filename]
end

Instance Method Details

#cache_keyObject (private)



34
35
36
# File 'lib/pry/method/patcher.rb', line 34

def cache_key
  "pry-redefined(0x#{method.owner.object_id.to_s(16)}##{method.name})"
end

#definition_for_owner(line) ⇒ String (private)

Update the definition line so that it can be eval'd directly on the Method's owner instead of from the original context.

In particular this takes def self.foo and turns it into def foo so that we don't end up creating the method on the singleton class of the singleton class by accident.

This is necessarily done by String manipulation because we can't find out what syntax is needed for the argument list by ruby-level introspection.

Parameters:

  • line (String)

    The original definition line. e.g. def self.foo(bar, baz=1)

Returns:

  • (String)

    The new definition line. e.g. def foo(bar, baz=1)



71
72
73
74
75
76
77
# File 'lib/pry/method/patcher.rb', line 71

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 #{method.original_name}` line to patch."
  end
end

#patch_in_ram(source) ⇒ Object

perform the patch



17
18
19
20
21
22
23
24
25
# File 'lib/pry/method/patcher.rb', line 17

def patch_in_ram(source)
  if method.alias?
    with_method_transaction do
      redefine source
    end
  else
    redefine source
  end
end

#redefine(source) ⇒ Object (private)



29
30
31
32
# File 'lib/pry/method/patcher.rb', line 29

def redefine(source)
  @@source_cache[cache_key] = source
  TOPLEVEL_BINDING.eval wrap(source), cache_key
end

#with_method_transactionObject (private)

Run some code ensuring that at the end target#meth_name will not have changed.

When we're redefining aliased methods we will overwrite the method at the unaliased name (so that super continues to work). By wrapping that code in a transation we make that not happen, which means that alias_method_chains, etc. continue to work.



45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/pry/method/patcher.rb', line 45

def with_method_transaction
  temp_name = "__pry_#{method.original_name}__"
  method = self.method
  method.owner.class_eval do
    alias_method temp_name, method.original_name
    yield
    alias_method method.name, method.original_name
    alias_method method.original_name, temp_name
  end

ensure
  method.send(:remove_method, temp_name) rescue nil
end

#wrap(source) ⇒ String (private)

Apply wrap_for_owner and wrap_for_nesting successively to source

Parameters:

  • source (String)

Returns:

  • (String)

    The wrapped source.



82
83
84
# File 'lib/pry/method/patcher.rb', line 82

def wrap(source)
  wrap_for_nesting(wrap_for_owner(source))
end

#wrap_for_nesting(source) ⇒ String (private)

Update the new source code to have the correct Module.nesting.

This method uses syntactic analysis of the original source file to determine the new nesting, so that we can tell the difference between:

class A; def self.b; end; end class << A; def b; end; end

The resulting code should be evaluated in the TOPLEVEL_BINDING.

Parameters:

  • source (String)

    The source to wrap.

Returns:

  • (String)


113
114
115
116
117
118
119
# File 'lib/pry/method/patcher.rb', line 113

def wrap_for_nesting(source)
  nesting = Pry::Code.from_file(method.source_file).nesting_at(method.source_line)

  (nesting + [source] + nesting.map{ "end" } + [""]).join(";")
rescue Pry::Indent::UnparseableNestingError
  source
end

#wrap_for_owner(source) ⇒ String (private)

Update the source code so that when it has the right owner when eval'd.

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.

Parameters:

  • source (String)

    The source to wrap

Returns:

  • (String)


94
95
96
97
98
99
# File 'lib/pry/method/patcher.rb', line 94

def wrap_for_owner(source)
  Pry.current[:pry_owner] = method.owner
  owner_source = definition_for_owner(source)
  visibility_fix = "#{method.visibility.to_s} #{method.name.to_sym.inspect}"
  "Pry.current[:pry_owner].class_eval do; #{owner_source}\n#{visibility_fix}\nend"
end