簡體   English   中英

如何通過命令行獲取 Ruby 類中定義的方法的詳細列表?

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

我正在尋找一種方法來生成 Ruby 類中所有方法的列表,類似於 C 頭文件。 我想要這個,這樣我就可以看到類的概述,而無需使用 IDE 代碼折疊來折疊所有內容。

最好這將是一個 *nix 命令行函數,以便我可以使用其他命令行工具進一步處理它。

輸出將類似於

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

類似於這個問題,除了我正在尋找一個包含參數的列表,我想輸出到命令行以便進一步處理。 目標是讓我在工作時可以查看此列表,因此我希望它一目了然。

對該類使用instance_methods然后查詢參數

例如

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

輸出:

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

您可能需要獲取子進程參數數組才能僅獲取名稱。

至於命令行,只需將其保存到 ruby​​ 腳本中即可。

如果你想獲得一個定義的所有方法Module ,你可以使用一個Module#instance_methods家庭的方法,這取決於,究竟是什么,你正在尋找:

每一個都有一個可選的布爾參數include_super=true ,它讓你決定是包含繼承的方法(默認)還是只從你將消息發送到的確切模塊中返回方法(當傳遞false )。

如果你想獲取這些方法的參數,你首先需要獲取一個代表你感興趣的方法的UnboundMethod反射代理對象。你可以使用Module#instance_method來做到這一點。

一旦有了UnboundMethod ,就可以使用UnboundMethod#parameters來獲取方法參數的描述。 但是請注意,您不會獲得可選參數的默認參數。 那實際上是不可能的。

使用這些構建塊,您可以構建如下內容:

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

你可以像這樣使用它:

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

但是,請注意,列出某個模塊的方法並不會為您提供該模塊的協議(即可以理解的消息集)!

以下是兩個簡單示例,其中模塊中定義的方法集與該模塊的實例理解的消息集不對應:

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

雖然這是一個愚蠢的例子,並且希望沒有人會真正編寫代碼,但它清楚地表明,雖然Foo有一個名為bar的方法,但它的實例不會以您期望的方式響應bar消息。

這是一個更現實的例子:

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!'

最后,萬一你希望你能找到一些瘋狂的元編程抽象解釋技巧,實際上可以讓你列出一個模塊的整個協議,讓我打消你的想法:

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

Voilà:一個類,它的實例響應無限數量的消息,事實上,它們響應每一個可能的消息 如果您認為這不切實際,那么實際上,Ruby 世界中使用最廣泛的庫之一就是這樣做的:ActiveRecord。

使用 TypeProf 創建 RBS 文件

對於 Ruby >= 2.7.1,正確的答案通常是使用RBSTypeProf創建(非常粗略的)頭文件的等效項。 由於您發布的代碼此時甚至不是類,並且不包含任何可推斷的類型信息,因此您的大多數類型都可能是“無類型的”,並且由您來填寫類型。

Ruby 本身不處理類型檢查 為此,您需要使用 Steep、Sorbet 或類似的東西。 也就是說,出於文檔目的,如果您還沒有良好的 YARD 文檔,TypeProf 可能是您最好的選擇,而 RBS 原型設計是合理的后備方案。 例如,給定以下 Ruby 源文件:

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

運行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

在可以通過 TypeProf 構建、解析 AST 和運行代碼路徑的實際代碼庫中,它在推斷常見類型方面做得相當合理,盡管有一些例外,並且它在某些元編程結構中表現不佳。 盡管如此,它在大多數情況下都能滿足您的要求。

不過老實說,除非您打算進行類型檢查,否則從文檔的角度來看,對@param@return使用 YARD 標簽通常會產生更有用的結果。 基於文檔的類型的問題在於必須積極維護文檔; 否則,文檔可能基於程序員的錯誤或疏忽而撒謊。 這就是 RBS 和 TypeProf 的優勢所在:它們基於實際代碼,而不是程序員編輯到文件中的注釋。 因此,您的里程將根據您的用例而有所不同。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM