Come posso ottenere dinamicamente il codice sorgente di un metodo e anche in quale file si trova questo metodo


89

Vorrei sapere se posso ottenere il codice sorgente di un metodo al volo e se posso ottenere in quale file è questo metodo.

piace

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

Risposte:


114

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

Notare che per i metodi incorporati, source_locationrestituisce nil. Se vuoi controllare il codice sorgente C (divertiti!), Dovrai cercare il file C giusto (sono più o meno organizzati per classe) e trovare rb_define_methodil metodo per (verso la fine del file ).

In Ruby 1.8 questo metodo non esiste, ma puoi usare questa gemma .


2
Ciao, vengo dal futuro, utilizzo Ruby 2.6.1! Voglio il codice sorgente di String#include?. Finora String.instance_method(:include?).source_locationritorna nil.
S.Goswami

39

Nessuna delle risposte finora mostra come visualizzare il codice sorgente di un metodo al volo ...

In realtà è molto semplice se usi la fantastica gem "method_source" di John Mair (il creatore di Pry): il metodo deve essere implementato in Ruby (non C) e deve essere caricato da un file (non irb).

Ecco un esempio che mostra il codice sorgente del metodo nella console di Rails con 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 

Guarda anche:


1
Ho sempre perso questa funzione in Ruby. Lisp può farlo :)
Tilo

Proveniente da Clojure's source. Funziona come previsto.
Sebastian Palma il

Ottengo questo errore: [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_locationfunziona bene però
Abram

17

Ecco come stampare il codice sorgente da ruby:

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

10

Senza dipendenze

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 vuoi usarlo più comodamente, puoi aprire la 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 poi chiama method.source

Con Pry puoi usare il show-methodper visualizzare una sorgente del metodo e puoi persino vedere del codice sorgente di ruby ​​c con pry-docinstallato, secondo il documento di pry in codde-browing

Nota che possiamo anche visualizzare i metodi C (da Ruby Core) usando il plugin pry-doc; mostriamo anche la sintassi alternativa per 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;
}

è un'ottima idea per un sourcemetodo all'interno della Methodclasse. Sarebbe ancora meglio se elaborasse il testo e sapesse quando interrompere la stampa perché ha raggiunto la fine del metodo.
Toby 1 Kenobi il

4

Ho creato la gemma "ri_for" per questo scopo

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

... genera la sorgente (e la posizione, se sei su 1.9).

GL. -r


Tutto ciò per me sta producendo un errore di segmentazione. :(
panzi

come riprodurre seg guasto? quale metodo / classe?
Rogerdpack

1

Ho dovuto implementare una funzione simile (afferrare il sorgente di un blocco) come parte di Wrong e puoi vedere come (e forse anche riutilizzare il codice) in chunk.rb (che si basa su RubyParser di Ryan Davis e su alcuni piuttosto divertenti codice glomming del file sorgente ). Dovresti modificarlo per usarlo Method#source_locatione forse modificare alcune altre cose in modo che includa o meno il file def.

A proposito, penso che Rubinius abbia questa caratteristica incorporata. Per qualche motivo è stata esclusa dalla MRI (l'implementazione standard di Ruby), da qui questo hack.

Oooh, mi piacciono alcune delle cose in method_source ! Come usare eval per dire se un'espressione è valida (e continuare a glommare le linee di origine finché non si smette di ricevere errori di analisi, come fa Chunk) ...


1

I metodi interni non hanno origine o posizione di origine (ad esempio Integer#to_s)

require 'method_source'
User.method(:last).source
User.method(:last).source_location
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.