Class: Pry::Method::Patcher

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

Constant Summary collapse

@@source_cache =

rubocop:disable Style/ClassVars

{}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(method) ⇒ Patcher

rubocop:enable Style/ClassVars


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

def initialize(method)
  @method = method
end

Instance Attribute Details

#methodObject

Returns the value of attribute method


6
7
8
# File 'lib/pry/method/patcher.rb', line 6

def method
  @method
end

Class Method Details

.code_for(filename) ⇒ Object


16
17
18
# File 'lib/pry/method/patcher.rb', line 16

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

Instance Method Details

#cache_keyObject (private)


38
39
40
# File 'lib/pry/method/patcher.rb', line 38

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)


78
79
80
81
82
83
84
85
86
# File 'lib/pry/method/patcher.rb', line 78

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


21
22
23
24
25
26
27
28
29
# File 'lib/pry/method/patcher.rb', line 21

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

#redefine(source) ⇒ Object (private)


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

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.


49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/pry/method/patcher.rb', line 49

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
  begin
    method.send(:remove_method, temp_name)
  rescue StandardError
    nil
  end
end

#wrap(source) ⇒ String (private)

Apply wrap_for_owner and wrap_for_nesting successively to source

Parameters:

  • source (String)

Returns:

  • (String)

    The wrapped source.


91
92
93
# File 'lib/pry/method/patcher.rb', line 91

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)

122
123
124
125
126
127
128
# File 'lib/pry/method/patcher.rb', line 122

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)

103
104
105
106
107
108
# File 'lib/pry/method/patcher.rb', line 103

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