Comment puis-je obtenir le code source d'une méthode de manière dynamique et également dans quel fichier cette méthode se trouve-t-elle?

89

Je voudrais savoir si je peux obtenir le code source d'une méthode à la volée, et si je peux obtenir dans quel fichier se trouve cette méthode.

comme

A.new.method(:a).SOURCE_CODE
A.new.method(:a).FILE
allenwei
la source

Réponses:

114

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

Notez que pour les méthodes intégrées, source_locationrenvoie nil. Si vous voulez vérifier le code source C (amusez-vous!), Vous devrez chercher le bon fichier C (ils sont plus ou moins organisés par classe) et trouver le rb_define_methodpour la méthode (vers la fin du fichier ).

Dans Ruby 1.8, cette méthode n'existe pas, mais vous pouvez utiliser cette gemme .

Marc-André Lafortune
la source
2
Salut, je viens du futur, en utilisant Ruby 2.6.1! Je veux le code source de String#include?. Jusqu'à présent String.instance_method(:include?).source_locationrevient nil.
S.Goswami
39

Aucune des réponses à ce jour ne montre comment afficher le code source d'une méthode à la volée ...

C'est en fait très facile si vous utilisez l'impressionnant joyau 'method_source' de John Mair (le créateur de Pry): La méthode doit être implémentée en Ruby (et non en C), et doit être chargée à partir d'un fichier (pas d'irb).

Voici un exemple affichant le code source de la méthode dans la console Rails avec 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 

Voir également:

Tilo
la source
1
J'ai toujours manqué cette fonctionnalité dans Ruby. Lisp peut le faire :)
Tilo
Venant du Clojure source. Cela fonctionne comme prévu.
Sebastian Palma
J'obtiens cette erreur: [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_locationfonctionne bien cependant
Abram
17

Voici comment imprimer le code source de ruby:

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

Sans dépendances

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]

Si vous souhaitez l'utiliser plus facilement, vous pouvez ouvrir 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

Et puis appelle method.source

Avec Pry, vous pouvez utiliser show-methodpour afficher une source de méthode, et vous pouvez même voir du code source ruby ​​c avec pry-docinstallé, selon la doc de pry dans pry codde-browing

Notez que nous pouvons également afficher les méthodes C (depuis Ruby Core) en utilisant le plugin pry-doc; nous montrons également la syntaxe alternative pour 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;
}
crocs
la source
c'est une excellente idée pour une sourceméthode à l'intérieur de la Methodclasse. Ce serait encore mieux s'il traitait le texte et de nouveau quand arrêter l'impression car il atteignait la fin de la méthode.
Toby 1 Kenobi le
4

J'ai créé la gemme "ri_for" à cet effet

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

... renvoie la source (et l'emplacement, si vous êtes sur 1.9).

GL. -r

rogerdpack
la source
Tout cela produit pour moi un défaut de segmentation. :(
panzi
comment reproduire la faute de seg? quelle méthode / classe?
rogerdpack
1

J'ai dû implémenter une fonctionnalité similaire (récupérer la source d'un bloc) dans le cadre de Wrong et vous pouvez voir comment (et peut-être même réutiliser le code) dans chunk.rb (qui repose sur RubyParser de Ryan Davis ainsi que sur certains assez drôles code glomming du fichier source ). Vous devrez le modifier pour l'utiliser Method#source_locationet peut-être ajuster d'autres choses pour qu'il inclue ou non le fichier def.

BTW Je pense que Rubinius a cette fonctionnalité intégrée. Pour une raison quelconque, elle a été laissée en dehors de l'IRM (l'implémentation standard de Ruby), d'où ce hack.

Oooh, j'aime certaines choses dans method_source ! Comme utiliser eval pour dire si une expression est valide (et continuer à glommer les lignes source jusqu'à ce que vous arrêtiez d'obtenir des erreurs d'analyse, comme le fait Chunk) ...

AlexChaffee
la source
1

Les méthodes internes n'ont pas de source ou d'emplacement source (par exemple Integer#to_s)

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