[英]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
家庭的方法,这取决于,究竟是什么,你正在寻找:
Module#public_instance_methods
for all public
methods, Module#public_instance_methods
用于所有public
方法,Module#protected_instance_methods
for all protected
methods, Module#protected_instance_methods
用于所有protected
方法,Module#instance_methods
for all public
and protected
methods, or Module#instance_methods
用于所有public
和protected
方法,或Module#private_instance_methods
for all private
methods. Module#private_instance_methods
用于所有private
方法。 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。
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,正确的答案通常是使用RBS或TypeProf创建(非常粗略的)头文件的等效项。 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.