Class: Pry::Method::Patcher
Constant Summary collapse
- @@source_cache =
rubocop:disable Style/ClassVars
{}
Instance Attribute Summary collapse
-
#method ⇒ Object
Returns the value of attribute method.
Class Method Summary collapse
Instance Method Summary collapse
- #cache_key ⇒ Object private
-
#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.
-
#initialize(method) ⇒ Patcher
constructor
rubocop:enable Style/ClassVars.
-
#patch_in_ram(source) ⇒ Object
perform the patch.
- #redefine(source) ⇒ Object private
-
#with_method_transaction ⇒ Object
private
Run some code ensuring that at the end target#meth_name will not have changed.
-
#wrap(source) ⇒ String
private
Apply wrap_for_owner and wrap_for_nesting successively to
source. -
#wrap_for_nesting(source) ⇒ String
private
Update the new source code to have the correct Module.nesting.
-
#wrap_for_owner(source) ⇒ String
private
Update the source code so that when it has the right owner when eval'd.
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
#method ⇒ Object
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_key ⇒ Object (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.
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_transaction ⇒ Object (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 translation 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
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.
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 still be defined in the correct place.
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 |