Como posso obter o código-fonte de um método dinamicamente e também em qual arquivo esse método está localizado

89

Gostaria de saber se posso obter o código-fonte de um método em tempo real e se posso obter em qual arquivo esse método está.

gostar

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

Respostas:

114

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"

Observe que para métodos embutidos, source_locationretorna nil. Se quiser verificar o código-fonte C (divirta-se!), Você terá que procurar o arquivo C correto (eles são mais ou menos organizados por classe) e encontrar o rb_define_methodpara o método (no final do arquivo )

No Ruby 1.8, este método não existe, mas você pode usar esta joia .

Marc-André Lafortune
fonte
2
Olá, sou do futuro usando Ruby 2.6.1! Eu quero o código-fonte de String#include?. Até agora String.instance_method(:include?).source_locationretorna nil.
S.Goswami
39

Nenhuma das respostas até agora mostra como exibir o código-fonte de um método em tempo real ...

Na verdade, é muito fácil se você usar a incrível joia 'method_source' de John Mair (o criador do Pry): O método deve ser implementado em Ruby (não em C) e deve ser carregado de um arquivo (não em irb).

Aqui está um exemplo exibindo o código-fonte do método no console Rails com 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 

Veja também:

Tilo
fonte
1
Sempre senti falta desse recurso no Ruby. Lisp pode fazer isso :)
Tilo
Vindo do Clojure source. Isso funciona conforme o esperado.
Sebastian Palma
Eu recebo este erro: [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'
Abrão
RSpec.method(:to_json).source_locationfunciona bem
Abrão
17

Aqui está como imprimir o código-fonte do Ruby:

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

Sem dependências

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]

Se você quiser usar isso de forma mais conveniente, pode abrir a Methodclasse:

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

E então é só ligar method.source

Com o Pry você pode usar o show-methodpara ver o código-fonte de um método, e você pode até mesmo ver algum código-fonte ruby ​​c com pry-docinstalado, de acordo com o documento do pry em codde-browing

Observe que também podemos visualizar métodos C (do Ruby Core) usando o plugin pry-doc; também mostramos a sintaxe alternativa para o método 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
fonte
essa é uma ótima ideia para um sourcemétodo dentro da Methodclasse. Seria ainda melhor se processasse o texto e novo quando parar de imprimir porque chegou ao fim do método.
Toby 1 Kenobi
4

Eu criei a gema "ri_for" para este propósito

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

... produz a fonte (e localização, se você estiver no 1.9).

GL. -r

rogerdpack
fonte
Tudo isso para mim está produzindo uma falha de segmentação. :(
panzi,
como reproduzir falha seg? qual método / classe?
rogerdpack,
1

Tive que implementar um recurso semelhante (pegar a fonte de um bloco) como parte do Wrong e você pode ver como (e talvez até mesmo reutilizar o código) em chunk.rb (que depende do RubyParser de Ryan Davis, bem como de alguns código de glomming do arquivo fonte ). Você teria que modificá-lo para usar Method#source_locatione talvez ajustar algumas outras coisas para que inclua ou não o def.

BTW, acho que o Rubinius tem esse recurso embutido. Por algum motivo, ele foi deixado de fora do MRI (a implementação padrão do Ruby), daí este hack.

Oooh, eu gosto de algumas das coisas em method_source ! É como usar eval para dizer se uma expressão é válida (e continuar glomming as linhas de origem até parar de receber erros de análise, como o Chunk faz) ...

AlexChaffee
fonte
1

Métodos internos não têm fonte ou localização de fonte (por exemplo Integer#to_s)

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