Jak mogę dynamicznie uzyskać kod źródłowy metody, a także w którym pliku znajduje się ta metoda

90

Chciałbym wiedzieć, czy mogę uzyskać kod źródłowy metody w locie i czy mogę uzyskać plik, w którym znajduje się ta metoda.

lubić

A.new.method(:a).SOURCE_CODE
A.new.method(:a).FILE
allenwei
źródło

Odpowiedzi:

117

Zastosowanie 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"

Zwróć uwagę, że w przypadku metod wbudowanych source_locationzwraca nil. Jeśli chcesz sprawdzić kod źródłowy C (baw się dobrze!), Musisz poszukać odpowiedniego pliku C (są one mniej więcej uporządkowane według klas) i znaleźć rb_define_methodmetodę (pod koniec pliku ).

W Ruby 1.8 ta metoda nie istnieje, ale możesz użyć tego klejnotu .

Marc-André Lafortune
źródło
2
Cześć, jestem z przyszłości i używam Rubiego 2.6.1! Chcę mieć kod źródłowy String#include?. Jak dotąd String.instance_method(:include?).source_locationwraca nil.
S.Goswami
39

Żadna z dotychczasowych odpowiedzi nie pokazuje, jak wyświetlić kod źródłowy metody w locie ...

W rzeczywistości jest to bardzo proste, jeśli użyjesz niesamowitego klejnotu „method_source” Johna Maira (twórcy Pry): Metoda musi być zaimplementowana w Rubim (nie C) i musi zostać załadowana z pliku (nie irb).

Oto przykład wyświetlania kodu źródłowego metody w konsoli Railsów z 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 

Zobacz też:

Tilo
źródło
1
Zawsze brakowało mi tej funkcji w Rubim. Lisp może to zrobić :)
Tilo
Pochodzą z Clojure's source. Działa to zgodnie z oczekiwaniami.
Sebastian Palma
Otrzymuję ten błąd: [1] pry(main)> RSpec.method(:class_exec).source MethodSource::SourceNotFoundError: Could not locate source for class_exec! from /home/vagrant/.bundle/foo/ruby/2.5.0/gems/method_source-0.9.2/lib/method_source.rb:24:in `source_helper'
Abram
RSpec.method(:to_json).source_locationdziała dobrze
Abram
17

Oto jak wydrukować kod źródłowy z ruby:

puts File.read(OBJECT_TO_GET.method(:METHOD_FROM).source_location[0])
Automatico
źródło
10

Bez zależności

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]

Jeśli chcesz korzystać z tego wygodniej, możesz otworzyć Methodklasę:

# ~/.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

A potem po prostu zadzwoń method.source

Z Pry możesz użyć show-methoddo wyświetlenia źródła metody, a nawet możesz zobaczyć kod źródłowy ruby ​​c z pry-doczainstalowanym, zgodnie z dokumentacją pry'a w codde-browing

Zauważ, że możemy również przeglądać metody C (z Ruby Core) używając wtyczki pry-doc; pokazujemy również alternatywną składnię metody show:

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;
}
fangxing
źródło
to świetny pomysł na sourcemetodę wewnątrz Methodklasy. Byłoby jeszcze lepiej, gdyby przetwarzał tekst i nowy, kiedy przerwać drukowanie, ponieważ doszedł do końca metody.
Toby 1 Kenobi
4

W tym celu stworzyłem gem „ri_for”

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

... wyświetla źródło (i lokalizację, jeśli jesteś na 1.9).

GL. -r

rogerdpack
źródło
Wszystko to dla mnie powoduje błąd segmentacji. :(
panzi
jak odtworzyć błąd seg? która metoda / klasa?
rogerdpack
1

Musiałem zaimplementować podobną funkcję (pobrać źródło bloku) jako część Wrong i możesz zobaczyć, jak (a może nawet ponownie użyć kodu) w chunk.rb (który opiera się na RubyParserze Ryana Davisa, a także całkiem zabawnych kod źródłowy pliku glomming ). Musisz go zmodyfikować, aby użyćMethod#source_location i być może dostosować kilka innych rzeczy, aby zawierał lub nie zawierał def.

BTW Myślę, że Rubinius ma wbudowaną tę funkcję. Z jakiegoś powodu została pominięta w MRI (standardowa implementacja Rubiego), stąd ten hack.

Oooh, podoba mi się niektóre rzeczy w method_source ! Podobnie jak używanie eval do stwierdzenia, czy wyrażenie jest prawidłowe (i kontynuuj przeglądanie wierszy źródłowych, dopóki nie przestaniesz otrzymywać błędów analizy, jak robi to Chunk) ...

AlexChaffee
źródło
1

Metody wewnętrzne nie mają źródła ani lokalizacji źródłowej (np. Integer#to_s)

require 'method_source'
User.method(:last).source
User.method(:last).source_location
dorycki
źródło