How can I get source code of a method dynamically

2019-01-04 08:02发布

问题:

I would like to know whether I can get source code a method on the fly, and whether I can get which file is this method in.

like

A.new.method(:a).SOURCE_CODE
A.new.method(:a).FILE

回答1:

Use source_location:

class A
  def foo
  end
end

file, line = A.instance_method(:foo).source_location
# or
file, line = A.new.method(:foo).source_location
puts "Method foo is defined in #{file}, line #{line}"
# => "Method foo is defined in temp.rb, line 2"

It is new to Ruby 1.9, though. For Ruby 1.8, you can use this gem, and I'll copy this code over to backports when I get a second.



回答2:

None of the answers so far show how to display the source code of a method on the fly...

It's actually very easy if you use the awesome 'method_source' gem by John Mair (the maker of Pry): The method has to be implemented in Ruby (not C), and has to be loaded from a file (not irb).

Here's an example displaying the method source code in the Rails console with method_source:

  $ rails console
  > require 'method_source'
  > I18n::Backend::Simple.instance_method(:lookup).source.display
    def lookup(locale, key, scope = [], options = {})
      init_translations unless initialized?
      keys = I18n.normalize_keys(locale, key, scope, options[:separator])

      keys.inject(translations) do |result, _key|
        _key = _key.to_sym
        return nil unless result.is_a?(Hash) && result.has_key?(_key)
        result = result[_key]
        result = resolve(locale, _key, result, options.merge(:scope => nil)) if result.is_a?(Symbol)
        result
      end
    end
    => nil 

See also:

  • https://rubygems.org/gems/method_source
  • https://github.com/banister/method_source
  • http://banisterfiend.wordpress.com/


回答3:

Here is how to print out the source code from ruby:

puts File.read(OBJECT_TO_GET.method(:METHOD_FROM).source_location[0])


回答4:

Without dependencies

method = SomeConstant.method(:some_method_name)
file_path, line = method.source_location
# puts 10 lines start from the method define 
IO.readlines(file_path)[line-1, 10]

If you want use this more conveniently, your can open the Method class:

# ~/.irbrc
class Method
  def source(limit=10)
    file, line = source_location
    if file && line
      IO.readlines(file)[line-1,limit]
    else
      nil
    end
  end
end

And then just call method.source

With Pry you can use the show-method to view a method source, and you can even see some ruby c source code with pry-doc installed, according pry's doc in codde-browing

Note that we can also view C methods (from Ruby Core) using the pry-doc plugin; we also show off the alternate syntax for show-method:

pry(main)> show-method Array#select

From: array.c in Ruby Core (C Method):
Number of lines: 15

static VALUE
rb_ary_select(VALUE ary)
{
    VALUE result;
    long i;

    RETURN_ENUMERATOR(ary, 0, 0);
    result = rb_ary_new2(RARRAY_LEN(ary));
    for (i = 0; i < RARRAY_LEN(ary); i++) {
        if (RTEST(rb_yield(RARRAY_PTR(ary)[i]))) {
            rb_ary_push(result, rb_ary_elt(ary, i));
        }
    }
    return result;
}


回答5:

I created the "ri_for" gem for this purpose

 >> require 'ri_for'
 >> A.ri_for :foo

... outputs the source (and location, if you're on 1.9).

GL. -r



回答6:

I had to implement a similar feature (grab the source of a block) as part of Wrong and you can see how (and maybe even reuse the code) in chunk.rb (which relies on Ryan Davis' RubyParser as well as some pretty funny source file glomming code). You'd have to modify it to use Method#source_location and maybe tweak some other things so it does or doesn't include the def.

BTW I think Rubinius has this feature built in. For some reason it's been left out of MRI (the standard Ruby implementation), hence this hack.

Oooh, I like some of the stuff in method_source! Like using eval to tell if an expression is valid (and keep glomming source lines until you stop getting parse errors, like Chunk does)...



回答7:

Internal methods don't have source or source location (e.g. Integer#to_s)

require 'method_source'
User.method(:last).source
User.method(:last).source_location


标签: