简体   繁体   English

如何通过命令行获取 Ruby 类中定义的方法的详细列表?

[英]How to get a detailed list of methods defined in a Ruby class via the command line?

I am looking for a way to generate a list of all methods within a Ruby Class, similar to a C Header File.我正在寻找一种方法来生成 Ruby 类中所有方法的列表,类似于 C 头文件。 I want this so that I can see an overview of the class without using IDE code folding to collapse everything.我想要这个,这样我就可以看到类的概述,而无需使用 IDE 代码折叠来折叠所有内容。

Preferably this would be a *nix command line function so that I can further process this using other command line tools.最好这将是一个 *nix 命令行函数,以便我可以使用其他命令行工具进一步处理它。

Output would be something like输出将类似于

def foo( a, b, c )
def bar
def baz( d )

Similar to this question except I am looking for a list that includes parameters and I would like to output to the command line so that it can be further processed.类似于这个问题,除了我正在寻找一个包含参数的列表,我想输出到命令行以便进一步处理。 The goal is to have this list viewable while I am working so I want it to be readable at a glance.目标是让我在工作时可以查看此列表,因此我希望它一目了然。

Use instance_methods for that class and then query the parameters对该类使用instance_methods然后查询参数

eg例如

class A 
  def foo
  end
  def bar(a,b,c)
  end
end


A.instance_methods(false).each do |s|
   print "def #{s}(#{A.instance_method(s).parameters})\n"
end

Output:输出:

def foo([])
def bar([[:req, :a], [:req, :b], [:req, :c]])

You might need to get subprocess the parameters array to get the name only.您可能需要获取子进程参数数组才能仅获取名称。

As for command line, just save it into a ruby script.至于命令行,只需将其保存到 ruby​​ 脚本中即可。

If you want to get all methods defined in a Module , you can use one of the Module#instance_methods family of methods, depending on what, exactly , you are looking for:如果你想获得一个定义的所有方法Module ,你可以使用一个Module#instance_methods家庭的方法,这取决于,究竟是什么,你正在寻找:

Each of these has an optional boolean parameter include_super=true , which lets you decide whether to include inherited methods (the default) or only return methods from the exact module you are sending the message to (when passing false ).每一个都有一个可选的布尔参数include_super=true ,它让你决定是包含继承的方法(默认)还是只从你将消息发送到的确切模块中返回方法(当传递false )。

If you want to get the parameters of those methods, you first need to obtain an UnboundMethod reflective proxy object which represents the method you are interested in. You can do this by using the Module#instance_method .如果你想获取这些方法的参数,你首先需要获取一个代表你感兴趣的方法的UnboundMethod反射代理对象。你可以使用Module#instance_method来做到这一点。

Once you have an UnboundMethod , you can use UnboundMethod#parameters to get a description of the method's parameters.一旦有了UnboundMethod ,就可以使用UnboundMethod#parameters来获取方法参数的描述。 Note, however, that you do not get the default argument of an optional parameter.但是请注意,您不会获得可选参数的默认参数。 That is actually impossible.那实际上是不可能的。

With these building blocks, you can build something like this:使用这些构建块,您可以构建如下内容:

class MethodHeaderFormatter
  private

  attr_accessor :name, :parameter_list

  def initialize(name, parameter_list)
    self.name = name
    self.parameter_list = MethodParameterListFormatter.new(parameter_list)
  end

  public

  def to_s = "def #{name}" + if parameter_list.empty? then '' else "(#{parameter_list})" end

  class MethodParameterListFormatter
    private

    attr_accessor :parameter_list

    def initialize(parameter_list)
      self.parameter_list = parameter_list.map(&MethodParameterFormatter.method(:[]))
    end

    public

    def empty? = parameter_list.empty?

    def to_s = parameter_list.join(', ')

    module MethodParameterFormatter
      private

      attr_accessor :name, :prefix, :suffix

      def initialize(name) = self.name = name

      public

      def self.[]((type, name)) = const_get(:"#{type.capitalize}MethodParameterFormatter").new(name)

      def to_s = "#{prefix}#{name}#{suffix}"

      class ReqMethodParameterFormatter; include MethodParameterFormatter end

      class OptMethodParameterFormatter
        include MethodParameterFormatter

        def initialize(name)
          super
          self.suffix = '=unknown'
        end
      end

      class RestMethodParameterFormatter
        include MethodParameterFormatter

        def initialize(name)
          super
          self.prefix = '*'
        end
      end

      class KeyreqMethodParameterFormatter
        include MethodParameterFormatter

        def initialize(name)
          super
          self.suffix = ':'
        end
      end

      class KeyMethodParameterFormatter
        include MethodParameterFormatter

        def initialize(name)
          super
          self.suffix = ': unknown'
        end
      end

      class KeyrestMethodParameterFormatter
        include MethodParameterFormatter

        def initialize(name)
          super
          self.prefix = '**'
        end
      end

      class BlockMethodParameterFormatter
        include MethodParameterFormatter

        def initialize(name)
          super
          self.prefix = '&'
        end
      end

      private_constant *constants
    end

    private_constant *constants
  end

  private_constant *constants
end

And you can use it like this:你可以像这样使用它:

module Test
  def foo(a, b, c) end
  def bar; end
  def baz(d) end
  def quux(m, o = 23, *r, k:, ok: 42, **kr, &b) end
  alias_method :blarf, :quux
  attr_accessor :frotz
end

puts Test.public_instance_methods(false).map { |meth| MethodHeaderFormatter.new(meth, Test.instance_method(meth).parameters) }
# def baz(d)
# def quux(m, o=unknown, *r, k:, ok: unknown, **kr, &b)
# def frotz=()
# def blarf(m, o=unknown, *r, k:, ok: unknown, **kr, &b)
# def frotz
# def foo(a, b, c)
# def bar

HOWEVER , please note that listing the methods of some module does not give you the protocol (ie the set of messages that are understood) of that module!但是,请注意,列出某个模块的方法并不会为您提供该模块的协议(即可以理解的消息集)!

Here are two simple examples where the set of methods defined in a module does not correspond to the set of messages understood by instances of that module:以下是两个简单示例,其中模块中定义的方法集与该模块的实例理解的消息集不对应:

class Foo
  def bar = raise(NoMethodError)
  def respond_to?(meth) = meth != :bar && super
end

foo = Foo.new
foo.respond_to?(:bar) #=> false
foo.bar               # NoMethodError

While this is a stupid example, and code that hopefully nobody would write for real, it clearly shows that while Foo has a method named bar , its instances do not respond to a bar message the way you would expect.虽然这是一个愚蠢的例子,并且希望没有人会真正编写代码,但它清楚地表明,虽然Foo有一个名为bar的方法,但它的实例不会以您期望的方式响应bar消息。

Here is a more realistic example:这是一个更现实的例子:

class Bar
  def method_missing(meth, *) = if meth == :foo then 'Fooooo!' else super end
  def respond_to_missing?(meth, *) = meth == :foo || super
end

bar = Bar.new
bar.respond_to?(:foo) #=> true
bar.foo               #=> 'Fooooo!'

And finally, just in case you get your hopes up that you can find some insane meta-programming abstract interpretation trick that actually lets you list out the entire protocol of a module, let me disabuse you of that notion:最后,万一你希望你能找到一些疯狂的元编程抽象解释技巧,实际上可以让你列出一个模块的整个协议,让我打消你的想法:

class Quux
  def method_missing(*); end
  def respond_to_missing?(*) = true
end

Voilà: a class whose instances respond to an infinite number of messages , in fact, they respond to every possible message . Voilà:一个类,它的实例响应无限数量的消息,事实上,它们响应每一个可能的消息 And if you think this is unrealistic, well, actually, something like this is what one of the most widely-used libraries in the Ruby universe does: ActiveRecord.如果您认为这不切实际,那么实际上,Ruby 世界中使用最广泛的库之一就是这样做的:ActiveRecord。

Use TypeProf to Create RBS Files使用 TypeProf 创建 RBS 文件

With Ruby >= 2.7.1, the correct answer is generally to use RBS or TypeProf to create the (very rough) equivalent of header files.对于 Ruby >= 2.7.1,正确的答案通常是使用RBSTypeProf创建(非常粗略的)头文件的等效项。 Since your posted code isn't even a class at this point, and doesn't contain any inferrable type information, most of your types will all likely be "untyped," and it will be up to you to fill in the types.由于您发布的代码此时甚至不是类,并且不包含任何可推断的类型信息,因此您的大多数类型都可能是“无类型的”,并且由您来填写类型。

Type checking is not handled by Ruby natively. Ruby 本身不处理类型检查 For that, you'll need to use Steep, Sorbet, or something similar.为此,您需要使用 Steep、Sorbet 或类似的东西。 That said, for documentation purposes, TypeProf is probably your best bet if you don't already have good YARD documentation, with RBS prototyping as a reasonable fallback.也就是说,出于文档目的,如果您还没有良好的 YARD 文档,TypeProf 可能是您最好的选择,而 RBS 原型设计是合理的后备方案。 For example, given the following Ruby source file:例如,给定以下 Ruby 源文件:

class Example
  def foo(a, b, c); end
  def bar; end
  def baz(d); end
end

running typeprof example.rb would yield:运行typeprof example.rb会产生:

# TypeProf 0.20.2

# Classes
class Example
  def foo: (untyped a, untyped b, untyped c) -> nil
  def bar: -> nil
  def baz: (untyped d) -> nil
end

On real code bases where an AST can be built, parsed, and code paths run via TypeProf, it does a fairly reasonable job of inferring common types, although there are a few exceptions and it doesn't do well with certain metaprogramming constructs.在可以通过 TypeProf 构建、解析 AST 和运行代码路径的实际代码库中,它在推断常见类型方面做得相当合理,尽管有一些例外,并且它在某些元编程结构中表现不佳。 Still, it delivers what you're asking for most of the time.尽管如此,它在大多数情况下都能满足您的要求。

To be honest though, unless you're planning to do type checking, using YARD tags for @param and @return will often yield more useful results from a documentation standpoint.不过老实说,除非您打算进行类型检查,否则从文档的角度来看,对@param@return使用 YARD 标签通常会产生更有用的结果。 The problem with documentation-based typing is that documentation has to be actively maintained;基于文档的类型的问题在于必须积极维护文档; otherwise, the documentation can lie based on programmer error or neglect.否则,文档可能基于程序员的错误或疏忽而撒谎。 That's where RBS and TypeProf have an advantage: they're based on the actual code, not on comments the programmer edits into the file.这就是 RBS 和 TypeProf 的优势所在:它们基于实际代码,而不是程序员编辑到文件中的注释。 So, your mileage will vary based on on your use case.因此,您的里程将根据您的用例而有所不同。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM