[英]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.