簡體   English   中英

在給定ruby文件的情況下獲取Ruby方法的開始和結束的行號

[英]Get line number of beginning and end of Ruby method given a ruby file

給定一個ruby文件,如何找到Ruby方法的開始和結束行?

例如說:

1 class Home
2   def initialize(color)
3     @color = color
4   end
5 end

給定文件home.rb和方法名initialize我希望接收(2,4)這是開始和結束行。

尋找end是棘手的。 我能想到的最好方法是使用解析器gem 基本上,您將把Ruby代碼解析為AST,然后遞歸遍歷其節點,直到找到類型為:def的第一個子節點為:initialize的節點:

require "parser/current"

def recursive_find(node, &block)
  return node if block.call(node)
  return nil unless node.respond_to?(:children) && !node.children.empty?
  node.children.each do |child_node|
    found = recursive_find(child_node, &block)
    return found if found
  end
  nil
end

src = <<END
  class Home
    def initialize(color)
      @color = color
    end
  end
END
ast = Parser::CurrentRuby.parse(src)

found = recursive_find(ast) do |node|
  node.respond_to?(:type) && node.type == :def && node.children[0] == :initialize
end

puts "Start: #{found.loc.first_line}"
puts "End: #{found.loc.last_line}"

# => Start: 2
#    End: 4

PS我本來會推薦標准庫中的Ripper模塊,但是據我所知,還沒有辦法從中獲得最終結果。

Ruby有一個source_location方法,它為您提供文件和起始行:

class Home
  def initialize(color)
    @color = color
  end
end

p Home.new(1).method(:initialize).source_location
# => ["test2.rb", 2]

要找到終點,也許要尋找下一個def或EOF。

Ruby源代碼不過是一個文本文件。 您可以使用linux命令查找方法行號

grep -nrw 'def initialize' home.rb | grep -oE '[0-9]+'

我假設該文件最多包含一個initialize方法的定義(盡管將這種方法推廣到其他方法並不困難),並且該方法的定義不包含語法錯誤。 對於任何一種提取正確行范圍的方法,可能都需要使用后一種假設。

唯一棘手的部分是找到包含end的行,該行是initialize方法的定義的最后一行。 我用過Kernel#eval來定位那條線。 自然地,每當要執行該方法時都必須謹慎,盡管在這里eval只是試圖編譯(而不執行)方法。

def get_start_end_offsets(fname)
  start = nil
  str = ''
  File.foreach(fname).with_index do |line, i|
    if start.nil?
      next unless line.lstrip.start_with?('def initialize')
      start = i
      str << line.lstrip.insert(4,'_')
    else
      str << line
      if line.strip == "end"
        begin
          rv = eval(str)
        rescue SyntaxError
          nil
        end
        return [start, i] unless rv.nil? 
      end
    end
  end
  nil
end

假設我們正在搜索如下創建的文件1

str = <<-_
class C
  def self.feline
    "cat"
  end
  def initialize(arr)
    @row_sums = arr.map do |row|
      row.reduce do |t,x|
        t+x
      end
    end
  end
  def speak(sound)
    puts sound
  end
end
_

FName = 'temp'
File.write(FName, str)
  #=> 203

我們首先搜索(去掉前導空格之后) "def initialize"開始的行。 那就是索引4處的行。 完成該方法定義的end一個索引是10 因此,我們希望該方法返回[4, 10]

讓我們看看這是否就是我們得到的。

p get_start_end_offsets(FName)
  #=> [4, 10]

說明

變量start等於開始def initialize的行的索引(在刪除前導空白之后)。 start最初為nil ,一直保持為nil直到找到"def initialize"行。 然后將start設置為該行的索引。

現在,我們尋找一條line例如line.strip #=> "end" 這可能是也可能不是方法終止的end 要確定它是否是我們eval包含從一個開始的所有行字符串def initialize到線等於end才發現。 如果eval引發SyntaxError異常,則end不會終止該方法。 該異常被挽救,並且返回nil 如果那end終止了該方法,則eval將返回:_initialize (這是事實)。 在那種情況下,該方法返回[start, i] ,其中i是該行的索引。 如果在文件中未找到initialize方法,則返回nil

我已經將"initialize"轉換為"_initialize"以禁止顯示警告(eval):1: warning: redefining Object#initialize may cause infinite loop

查看此SO問題的兩個答案,以了解為什么要挽救SyntaxError

比較縮進

如果知道"def initialize..."總是縮進與終止方法定義的"end"行相同的縮進量(並且兩者之間沒有其他"end"行縮進相同),我們可以使用事實,以獲取開始和結束行。 有很多方法可以做到這一點。 我將使用Ruby有點晦澀的觸發器運算符。 這種方法將容忍語法錯誤。

def get_start_end_offsets(fname)
  indent = -1
  lines = File.foreach(fname).with_index.select do |line, i|
    cond1 = line.lstrip.start_with?('def initialize')
    indent = line.size - line.lstrip.size if cond1
    cond2 = line.strip == "end" && line.size - line.lstrip.size == indent
    cond1 .. cond2 ? true : false
  end
  return nil if lines.nil?
  lines.map(&:last).minmax
end

get_start_end_offsets(FName)
  #=> [4, 10] 

1該文件不必只包含代碼。

暫無
暫無

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

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